diff --git a/apps/data_handler/db/crud.py b/apps/data_handler/db/crud.py index 8a104301..d5469a5c 100644 --- a/apps/data_handler/db/crud.py +++ b/apps/data_handler/db/crud.py @@ -19,9 +19,13 @@ ) from data_handler.db.models.nostra_events import ( BearingCollateralBurnEventModel, + BearingCollateralMintEventModel, DebtBurnEventModel, DebtMintEventModel, DebtTransferEventModel, + InterestRateModelEventModel, + NonInterestBearingCollateralBurnEventModel, + NonInterestBearingCollateralMintEventModel, ) from data_handler.db.models.zklend_events import ( AccumulatorsSyncEventModel, @@ -415,6 +419,47 @@ def get_all_block_records(self, model: Type[ModelType] = None) -> Query: finally: db.close() + def get_all_events_from_models( + self, + models: List[Type[ModelType]], + protocol_id: Optional[str] = None, + event_name: Optional[str] = None, + block_number: Optional[int] = None, + ) -> List[Base]: + """ + Retrieves events from multiple models based on filtering criteria. + + :param models: List of SQLAlchemy models to query. + :param protocol_id: Optional protocol ID to filter by. + :param event_name: Optional event name to filter by. + :param block_number: Optional block number to filter by. + :return: A combined list of events from all specified models. + """ + events = [] + db = self.Session() + try: + for model in models: + model_filters = [] + if protocol_id: + model_filters.append(model.protocol_id == protocol_id) + if event_name: + model_filters.append(model.event_name == event_name) + if block_number: + model_filters.append(model.block_number == block_number) + + query = db.query(model) + if model_filters: + query = query.filter(*model_filters) + + fetched_events = query.all() + events.extend(fetched_events) + return events + except SQLAlchemyError as e: + logger.error(f"Error retrieving events from models {models}: {e}") + raise e + finally: + db.close() + class InitializerDBConnector: """ @@ -616,24 +661,20 @@ def create_accumulator_event( :param event_data: A dictionary containing 'token', 'lending_accumulator', and 'debt_accumulator'. """ - db = self.Session() + event = AccumulatorsSyncEventModel( + protocol_id=protocol_id, + event_name=event_name, + block_number=block_number, + token=event_data.get("token"), + lending_accumulator=event_data.get("lending_accumulator"), + debt_accumulator=event_data.get("debt_accumulator"), + ) try: - event = AccumulatorsSyncEventModel( - protocol_id=protocol_id, - event_name=event_name, - block_number=block_number, - token=event_data.get("token"), - lending_accumulator=event_data.get("lending_accumulator"), - debt_accumulator=event_data.get("debt_accumulator"), - ) - db.add(event) - db.commit() + self.write_to_db(event) + logger.info(f"AccumulatorsSyncEvent saved: {event}") except SQLAlchemyError as e: - db.rollback() logger.error(f"Error creating AccumulatorsSyncEventModel: {e}") raise e - finally: - db.close() def create_liquidation_event( self, protocol_id: str, event_name: str, block_number: int, event_data: dict @@ -647,28 +688,24 @@ def create_liquidation_event( 'debt_raw_amount', 'debt_face_amount', 'collateral_token', and 'collateral_amount'. """ - db = self.Session() + event = LiquidationEventModel( + protocol_id=protocol_id, + event_name=event_name, + block_number=block_number, + liquidator=event_data.get("liquidator"), + user=event_data.get("user"), + debt_token=event_data.get("debt_token"), + debt_raw_amount=event_data.get("debt_raw_amount"), + debt_face_amount=event_data.get("debt_face_amount"), + collateral_token=event_data.get("collateral_token"), + collateral_amount=event_data.get("collateral_amount"), + ) try: - event = LiquidationEventModel( - protocol_id=protocol_id, - event_name=event_name, - block_number=block_number, - liquidator=event_data.get("liquidator"), - user=event_data.get("user"), - debt_token=event_data.get("debt_token"), - debt_raw_amount=event_data.get("debt_raw_amount"), - debt_face_amount=event_data.get("debt_face_amount"), - collateral_token=event_data.get("collateral_token"), - collateral_amount=event_data.get("collateral_amount"), - ) - db.add(event) - db.commit() + self.write_to_db(event) + logger.info(f"LiquidationEvent saved: {event}") except SQLAlchemyError as e: - db.rollback() logger.error(f"Error creating LiquidationEventModel: {e}") raise e - finally: - db.close() def create_repayment_event( self, protocol_id: str, event_name: str, block_number: int, event_data: dict @@ -680,26 +717,22 @@ def create_repayment_event( :param block_number: The block number associated with the event. :param event_data: A dictionary containing 'user', 'amount'. """ - db = self.Session() + event = RepaymentEventModel( + protocol_id=protocol_id, + event_name=event_name, + block_number=block_number, + repayer=event_data.get("repayer"), + beneficiary=event_data.get("beneficiary"), + token=event_data.get("token"), + raw_amount=event_data.get("raw_amount"), + face_amount=event_data.get("face_amount"), + ) try: - event = RepaymentEventModel( - protocol_id=protocol_id, - event_name=event_name, - block_number=block_number, - repayer=event_data.get("repayer"), - beneficiary=event_data.get("beneficiary"), - token=event_data.get("token"), - raw_amount=event_data.get("raw_amount"), - face_amount=event_data.get("face_amount"), - ) - db.add(event) - db.commit() + self.write_to_db(event) + logger.info(f"RepaymentEvent saved: {event}") except SQLAlchemyError as e: - db.rollback() logger.error(f"Error creating RepaymentEventModel: {e}") raise e - finally: - db.close() def create_borrowing_event( self, protocol_id: str, event_name: str, block_number: int, event_data: dict @@ -711,25 +744,21 @@ def create_borrowing_event( :param block_number: The block number associated with the event. :param event_data: A dictionary containing 'user', 'token', 'raw_amount', 'face_amount'. """ - db = self.Session() + event = BorrowingEventModel( + protocol_id=protocol_id, + event_name=event_name, + block_number=block_number, + user=event_data.get("user"), + token=event_data.get("token"), + raw_amount=event_data.get("raw_amount"), + face_amount=event_data.get("face_amount"), + ) try: - event = BorrowingEventModel( - protocol_id=protocol_id, - event_name=event_name, - block_number=block_number, - user=event_data.get("user"), - token=event_data.get("token"), - raw_amount=event_data.get("raw_amount"), - face_amount=event_data.get("face_amount"), - ) - db.add(event) - db.commit() + self.write_to_db(event) + logger.info(f"BorrowingEvent saved: {event}") except SQLAlchemyError as e: - db.rollback() logger.error(f"Error creating BorrowingEventModel: {e}") raise e - finally: - db.close() def create_deposit_event( self, protocol_id: str, event_name: str, block_number: int, event_data: dict @@ -741,20 +770,18 @@ def create_deposit_event( :param block_number: The block number associated with the event. :param event_data: A dictionary containing 'user', 'token', 'face_amount'. """ - db = self.Session() + event = DepositEventModel( + protocol_id=protocol_id, + event_name=event_name, + block_number=block_number, + user=event_data.get("user"), + token=event_data.get("token"), + face_amount=event_data.get("face_amount"), + ) try: - event = DepositEventModel( - protocol_id=protocol_id, - event_name=event_name, - block_number=block_number, - user=event_data.get("user"), - token=event_data.get("token"), - face_amount=event_data.get("face_amount"), - ) - db.add(event) - db.commit() + self.write_to_db(event) + logger.info(f"DepositEvent saved: {event}") except SQLAlchemyError as e: - db.rollback() logger.error(f"Error creating DepositEventModel: {e}") raise e @@ -768,24 +795,20 @@ def create_withdrawal_event( :param block_number: The block number associated with the event. :param event_data: A dictionary containing 'user', 'token', 'face_amount'. """ - db = self.Session() + event = WithdrawalEventModel( + protocol_id=protocol_id, + event_name=event_name, + block_number=block_number, + user=event_data.get("user"), + token=event_data.get("token"), + amount=event_data.get("amount"), + ) try: - event = WithdrawalEventModel( - protocol_id=protocol_id, - event_name=event_name, - block_number=block_number, - user=event_data.get("user"), - token=event_data.get("token"), - amount=event_data.get("amount"), - ) - db.add(event) - db.commit() + self.write_to_db(event) + logger.info(f"WithdrawalEvent saved: {event}") except SQLAlchemyError as e: - db.rollback() logger.error(f"Error creating WithdrawalEventModel: {e}") raise e - finally: - db.close() def create_collateral_enabled_disabled_event( self, protocol_id: str, event_name: str, block_number: int, event_data: dict @@ -797,23 +820,19 @@ def create_collateral_enabled_disabled_event( :param block_number: The block number associated with the event. :param event_data: A dictionary containing 'user', 'token'. """ - db = self.Session() + event = CollateralEnabledDisabledEventModel( + protocol_id=protocol_id, + event_name=event_name, + block_number=block_number, + user=event_data.get("user"), + token=event_data.get("token"), + ) try: - event = CollateralEnabledDisabledEventModel( - protocol_id=protocol_id, - event_name=event_name, - block_number=block_number, - user=event_data.get("user"), - token=event_data.get("token"), - ) - db.add(event) - db.commit() + self.write_to_db(event) + logger.info(f"CollateralEnabledDisabledEvent saved: {event}") except SQLAlchemyError as e: - db.rollback() logger.error(f"Error creating CollateralEnabledDisabledEventModel: {e}") raise e - finally: - db.close() def get_all_events( self, @@ -829,67 +848,75 @@ def get_all_events( :param block_number: Optional block number to filter by. :return: A list of events matching the criteria. """ - db = self.Session() - try: + event_models = [ + AccumulatorsSyncEventModel, + LiquidationEventModel, + RepaymentEventModel, + BorrowingEventModel, + DepositEventModel, + WithdrawalEventModel, + CollateralEnabledDisabledEventModel, + ] - def apply_filters(query): - """ - Inner function to apply filters based on the provided parameters. - :param query: The SQLAlchemy query object. - :return: The query with filters applied. - """ - if protocol_id is not None: - query = query.filter_by(protocol_id=protocol_id) - if event_name is not None: - query = query.filter_by(event_name=event_name) - if block_number is not None: - query = query.filter_by(block_number=block_number) - return query - - accumulator_query = apply_filters(db.query(AccumulatorsSyncEventModel)) - liquidation_query = apply_filters(db.query(LiquidationEventModel)) - combined_query = accumulator_query.union(liquidation_query) - events = combined_query.all() + try: + events = self.get_all_events_from_models( + models=event_models, + protocol_id=protocol_id, + event_name=event_name, + block_number=block_number, + ) return events - except SQLAlchemyError as e: - logger.error(f"Error retrieving events: {e}") + logger.error(f"Error retrieving ZkLend events: {e}") raise e - finally: - db.close() - class NostraEventDBConnector(DBConnector): """ - Provides CRUD operations specifically for Nostra events, such as DebtMint, DebtBurn, DebtTransfer, - BearingCollateralMint, BearingCollateralBurn, and InterestRateModelUpdate events. + Provides CRUD operations specifically for Nostra events, such as + BearingCollateralBurn, BearingCollateralMint, DebtMint, DebtBurn, DebtTransfer, + InterestRateModelUpdate, NonInterestBearingCollateralMint and NonInterestBearingCollateralMint events. """ - def create_debt_mint_event( + def create_bearing_collateral_burn_event( self, protocol_id: str, event_name: str, block_number: int, event_data: dict ) -> None: """ - Creates a DebtMintEventModel record in the database. + Creates a BearingCollateralBurnEventModel record in the database. """ - db = self.Session() + event = BearingCollateralBurnEventModel( + protocol_id=protocol_id, + event_name=event_name, + block_number=block_number, + user=event_data.get("user"), + amount=event_data.get("amount"), + ) try: - event = DebtMintEventModel( - protocol_id=protocol_id, - event_name=event_name, - block_number=block_number, - user=event_data.get("user"), - amount=event_data.get("amount"), - ) - db.add(event) - db.commit() - logger.info(f"DebtMint event saved: {event}") + self.write_to_db(event) + logger.info(f"BearingCollateralBurn event saved: {event}") except SQLAlchemyError as e: - db.rollback() - logger.error(f"Error creating DebtMintEventModel: {e}") + logger.error(f"Error creating BearingCollateralBurnEventModel: {e}") + raise e + + def create_bearing_collateral_mint_event( + self, protocol_id: str, event_name: str, block_number: int, event_data: dict + ) -> None: + """ + Creates a BearingCollateralMintEventModel record in the database. + """ + event = BearingCollateralMintEventModel( + protocol_id=protocol_id, + event_name=event_name, + block_number=block_number, + user=event_data.get("user"), + amount=event_data.get("amount"), + ) + try: + self.write_to_db(event) + logger.info(f"BearingCollateralMint event saved: {event}") + except SQLAlchemyError as e: + logger.error(f"Error creating BearingCollateralMintEventModel: {e}") raise e - finally: - db.close() def create_debt_burn_event( self, protocol_id: str, event_name: str, block_number: int, event_data: dict @@ -897,24 +924,39 @@ def create_debt_burn_event( """ Creates a DebtBurnEventModel record in the database. """ - db = self.Session() + event = DebtBurnEventModel( + protocol_id=protocol_id, + event_name=event_name, + block_number=block_number, + user=event_data.get("user"), + amount=event_data.get("amount"), + ) try: - event = DebtBurnEventModel( - protocol_id=protocol_id, - event_name=event_name, - block_number=block_number, - user=event_data.get("user"), - amount=event_data.get("amount"), - ) - db.add(event) - db.commit() + self.write_to_db(event) logger.info(f"DebtBurn event saved: {event}") except SQLAlchemyError as e: - db.rollback() logger.error(f"Error creating DebtBurnEventModel: {e}") raise e - finally: - db.close() + + def create_debt_mint_event( + self, protocol_id: str, event_name: str, block_number: int, event_data: dict + ) -> None: + """ + Creates a DebtMintEventModel record in the database. + """ + event = DebtMintEventModel( + protocol_id=protocol_id, + event_name=event_name, + block_number=block_number, + user=event_data.get("user"), + amount=event_data.get("amount"), + ) + try: + self.write_to_db(event) + logger.info(f"DebtMint event saved: {event}") + except SQLAlchemyError as e: + logger.error(f"Error creating DebtMintEventModel: {e}") + raise e def create_debt_transfer_event( self, protocol_id: str, event_name: str, block_number: int, event_data: dict @@ -922,50 +964,86 @@ def create_debt_transfer_event( """ Creates a DebtTransferEventModel record in the database. """ - db = self.Session() + event = DebtTransferEventModel( + protocol_id=protocol_id, + event_name=event_name, + block_number=block_number, + sender=event_data.get("sender"), + recipient=event_data.get("recipient"), + amount=event_data.get("amount"), + ) try: - event = DebtTransferEventModel( - protocol_id=protocol_id, - event_name=event_name, - block_number=block_number, - sender=event_data.get("sender"), - recipient=event_data.get("recipient"), - amount=event_data.get("amount"), - ) - db.add(event) - db.commit() + self.write_to_db(event) logger.info(f"DebtTransfer event saved: {event}") except SQLAlchemyError as e: - db.rollback() logger.error(f"Error creating DebtTransferEventModel: {e}") raise e - finally: - db.close() - def create_bearing_collateral_burn_event( + def create_interest_rate_model_event( self, protocol_id: str, event_name: str, block_number: int, event_data: dict ) -> None: """ - Creates a BearingCollateralBurnEventModel record in the database. + Creates a InterestRateModelEventModel record in the database. """ - db = self.Session() + event = InterestRateModelEventModel( + protocol_id=protocol_id, + event_name=event_name, + block_number=block_number, + debt_token=event_data.get("debt_token"), + lending_index=event_data.get("lending_index"), + borrow_index=event_data.get("borrow_index"), + ) try: - event = BearingCollateralBurnEventModel( - protocol_id=protocol_id, - event_name=event_name, - block_number=block_number, - user=event_data.get("user"), - amount=event_data.get("amount"), + self.write_to_db(event) + logger.info(f"InterestRateModel event saved: {event}") + except SQLAlchemyError as e: + logger.error(f"Error creating InterestRateModelEventModel: {e}") + raise e + + def create_non_interest_bearing_collateral_burn_event( + self, protocol_id: str, event_name: str, block_number: int, event_data: dict + ) -> None: + """ + Creates a NonInterestBearingCollateralBurnEventModel record in the database. + """ + event = NonInterestBearingCollateralBurnEventModel( + protocol_id=protocol_id, + event_name=event_name, + block_number=block_number, + user=event_data.get("user"), + amount=event_data.get("amount"), + ) + try: + self.write_to_db(event) + logger.info(f"NonInterestBearingCollateralBurn event saved: {event}") + except SQLAlchemyError as e: + logger.error( + f"Error creating NonInterestBearingCollateralBurnEventModel: {e}" ) - db.add(event) - db.commit() - logger.info(f"BearingCollateralBurn event saved: {event}") + raise e + + def create_non_interest_bearing_collateral_mint_event( + self, protocol_id: str, event_name: str, block_number: int, event_data: dict + ) -> None: + """ + Creates a NonInterestBearingCollateralMintEventModel record in the database. + """ + event = NonInterestBearingCollateralMintEventModel( + protocol_id=protocol_id, + event_name=event_name, + block_number=block_number, + sender=event_data.get("sender"), + recipient=event_data.get("recipient"), + amount=event_data.get("amount"), + ) + try: + self.write_to_db(event) + logger.info(f"NonInterestBearingCollateralMint event saved: {event}") except SQLAlchemyError as e: - db.rollback() - logger.error(f"Error creating BearingCollateralBurnEventModel: {e}") + logger.error( + f"Error creating NonInterestBearingCollateralMintEventModel: {e}" + ) raise e - finally: - db.close() def get_all_events( self, @@ -976,43 +1054,25 @@ def get_all_events( """ Retrieves all types of Nostra events based on filtering criteria. """ - db = self.Session() - try: - - def apply_filters(query, model): - if protocol_id is not None: - query = query.filter(model.protocol_id == protocol_id) - if event_name is not None: - query = query.filter(model.event_name == event_name) - if block_number is not None: - query = query.filter(model.block_number == block_number) - return query - - debt_mint_query = apply_filters( - db.query(DebtMintEventModel), DebtMintEventModel - ) - debt_burn_query = apply_filters( - db.query(DebtBurnEventModel), DebtBurnEventModel - ) - debt_transfer_query = apply_filters( - db.query(DebtTransferEventModel), DebtTransferEventModel - ) - bearing_collateral_burn_query = apply_filters( - db.query(BearingCollateralBurnEventModel), - BearingCollateralBurnEventModel, - ) + event_models = [ + BearingCollateralBurnEventModel, + BearingCollateralMintEventModel, + DebtBurnEventModel, + DebtMintEventModel, + DebtTransferEventModel, + InterestRateModelEventModel, + NonInterestBearingCollateralBurnEventModel, + NonInterestBearingCollateralMintEventModel, + ] - combined_query = debt_mint_query.union_all( - debt_burn_query, - debt_transfer_query, - bearing_collateral_burn_query, + try: + events = self.get_all_events_from_models( + models=event_models, + protocol_id=protocol_id, + event_name=event_name, + block_number=block_number, ) - events = combined_query.all() return events - except SQLAlchemyError as e: logger.error(f"Error retrieving Nostra events: {e}") raise e - - finally: - db.close() diff --git a/apps/data_handler/db/models/nostra_events.py b/apps/data_handler/db/models/nostra_events.py index f096313a..449a66c1 100644 --- a/apps/data_handler/db/models/nostra_events.py +++ b/apps/data_handler/db/models/nostra_events.py @@ -1,43 +1,57 @@ from decimal import Decimal + from data_handler.db.models.event import EventBaseModel from sqlalchemy import Numeric, String from sqlalchemy.orm import Mapped, mapped_column -class DebtMintEventModel(EventBaseModel): +class BearingCollateralBurnEventModel(EventBaseModel): """ - Database model for DebtMint event, inheriting from EventBaseModel. + Database model for BearingCollateralBurn event, inheriting from EventBaseModel. + + This model stores the user address and the amount burned. + """ + + __tablename__ = "bearing_collateral_burn_event" + + user: Mapped[str] = mapped_column(String, nullable=False) + amount: Mapped[Decimal] = mapped_column(Numeric(38, 18), nullable=False) + + +class BearingCollateralMintEventModel(EventBaseModel): + """ + Database model for BearingCollateral mint event, inheriting from EventBaseModel. This model stores the user address and the amount minted. """ - __tablename__ = "debt_mint_event" + __tablename__ = "bearing_collateral_mint_event" user: Mapped[str] = mapped_column(String, nullable=False) amount: Mapped[Decimal] = mapped_column(Numeric(38, 18), nullable=False) -class DebtBurnEventModel(EventBaseModel): +class DebtMintEventModel(EventBaseModel): """ - Database model for DebtBurn event, inheriting from EventBaseModel. + Database model for DebtMint event, inheriting from EventBaseModel. - This model stores the user address and the amount burned. + This model stores the user address and the amount minted. """ - __tablename__ = "debt_burn_event" + __tablename__ = "debt_mint_event" user: Mapped[str] = mapped_column(String, nullable=False) amount: Mapped[Decimal] = mapped_column(Numeric(38, 18), nullable=False) -class BearingCollateralBurnEventModel(EventBaseModel): +class DebtBurnEventModel(EventBaseModel): """ - Database model for BearingCollateralBurn event, inheriting from EventBaseModel. + Database model for DebtBurn event, inheriting from EventBaseModel. This model stores the user address and the amount burned. """ - __tablename__ = "bearing_collateral_burn_event" + __tablename__ = "debt_burn_event" user: Mapped[str] = mapped_column(String, nullable=False) amount: Mapped[Decimal] = mapped_column(Numeric(38, 18), nullable=False) @@ -74,14 +88,24 @@ class InterestRateModelEventModel(EventBaseModel): borrow_index: Mapped[Decimal] = mapped_column(Numeric(38, 18), nullable=False) -class BearingCollateralMintEventModel(EventBaseModel): +class NonInterestBearingCollateralMintEventModel(EventBaseModel): + """ + Database model for NonInterestBearingCollateralMint event. """ - Database model for BearingCollateral mint event, inheriting from EventBaseModel. - This model stores the user address and the amount minted. + __tablename__ = "non_interest_bearing_collateral_mint_event" + + sender: Mapped[str] = mapped_column(String, nullable=False) + recipient: Mapped[str] = mapped_column(String, nullable=False) + amount: Mapped[Decimal] = mapped_column(Numeric(38, 18), nullable=False) + + +class NonInterestBearingCollateralBurnEventModel(EventBaseModel): + """ + Database model for NonInterestBearingCollateralBurn event. """ - __tablename__ = "bearing_collateral_mint_event" + __tablename__ = "non_interest_bearing_collateral_burn_event" user: Mapped[str] = mapped_column(String, nullable=False) amount: Mapped[Decimal] = mapped_column(Numeric(38, 18), nullable=False) diff --git a/apps/data_handler/handler_tools/data_parser/nostra.py b/apps/data_handler/handler_tools/data_parser/nostra.py index 94a19801..075f5b6f 100644 --- a/apps/data_handler/handler_tools/data_parser/nostra.py +++ b/apps/data_handler/handler_tools/data_parser/nostra.py @@ -29,19 +29,18 @@ def parse_interest_rate_model_event( Parses the interest rate model event data into a human-readable format. The event data is fetched from on-chain logs and is structured in the following way: - event_data[0]: The debt token address (as a hexadecimal string). - - event_data[5]: The lending interest rate index (as a hexadecimal in 18 decimal places). - - event_data[7]: The borrow interest rate index (as a hexadecimal in 18 decimal places). + - event_data[1]: The lending interest rate index (as a hexadecimal in 18 decimal places). + - event_data[2]: The borrow interest rate index (as a hexadecimal in 18 decimal places). Args: event_data (List[Any]): A list containing the raw event data. - Expected order: [debt_token, lending_rate, _, borrow_rate, _, - lending_index, _, borrow_index, _] + Expected order: [debt_token, lending_index, _, borrow_index, _] Returns: InterestRateModelEventData: A model with the parsed event data. """ return InterestRateModelEventData( debt_token=event_data[0], - lending_index=event_data[5], - borrow_index=event_data[7], + lending_index=event_data[1], + borrow_index=event_data[2], ) @classmethod @@ -64,7 +63,9 @@ def parse_non_interest_bearing_collateral_mint_event( NonInterestBearingCollateralMintEventData: A model with the parsed event data. """ return NonInterestBearingCollateralMintEventData( - sender=event_data[0], recipient=event_data[1], raw_amount=event_data[2] + sender=event_data[0], + recipient=event_data[1], + raw_amount=event_data[2], ) @classmethod @@ -86,7 +87,8 @@ def parse_non_interest_bearing_collateral_burn_event( NonInterestBearingCollateralBurnEventData: A model with the parsed event data. """ return NonInterestBearingCollateralBurnEventData( - user=event_data[0], face_amount=event_data[1] + user=event_data[0], + face_amount=event_data[1], ) def parse_interest_bearing_collateral_mint_event( @@ -130,7 +132,7 @@ def parse_interest_bearing_collateral_burn_event( """ return BearingCollateralBurnEventData( user=event_data[0], - amount=Decimal(int(event_data[1], 16)), + amount=event_data[1], ) def parse_debt_transfer_event(self, event_data: List[Any]) -> DebtTransferEventData: @@ -148,7 +150,7 @@ def parse_debt_transfer_event(self, event_data: List[Any]) -> DebtTransferEventD return DebtTransferEventData( sender=event_data[0], recipient=event_data[1], - amount=Decimal(int(event_data[2], 16)), + amount=event_data[2], ) @classmethod @@ -166,7 +168,7 @@ def parse_debt_mint_event(cls, event_data: list[Any]) -> DebtMintEventData: """ return DebtMintEventData( user=event_data[0], - amount=Decimal(int(event_data[1], 16)), + amount=event_data[1], ) @classmethod @@ -184,5 +186,5 @@ def parse_debt_burn_event(cls, event_data: list[Any]) -> DebtBurnEventData: """ return DebtBurnEventData( user=event_data[0], - amount=Decimal(int(event_data[1], 16)), + amount=event_data[1], ) diff --git a/apps/data_handler/handler_tools/data_parser/serializers/nostra.py b/apps/data_handler/handler_tools/data_parser/serializers/nostra.py index 62f5e1ee..e974792f 100644 --- a/apps/data_handler/handler_tools/data_parser/serializers/nostra.py +++ b/apps/data_handler/handler_tools/data_parser/serializers/nostra.py @@ -1,4 +1,5 @@ from decimal import Decimal + from pydantic import BaseModel, ValidationInfo, field_validator from shared.helpers import add_leading_zeros @@ -34,6 +35,25 @@ def validate_address(cls, value: str, info: ValidationInfo) -> str: raise ValueError(f"Invalid address provided for {info.field_name}") return add_leading_zeros(value) + @field_validator("amount", mode="before") + def validate_numeric_string(cls, value: str, info: ValidationInfo) -> Decimal: + """ + Validates that the provided amount is a valid hexadecimal string and converts it to a Decimal. + Args: + value (str): The amount string to validate. + info (ValidationInfo): Validation context information. + Raises: + ValueError: If the provided amount is not a valid hexadecimal number. + Returns: + Decimal: The validated and converted amount as a Decimal. + """ + try: + return Decimal(int(value, 16)) + except ValueError: + raise ValueError( + f"{info.field_name} field is not a valid hexadecimal number" + ) + class DebtBurnEventData(BaseModel): """ @@ -66,6 +86,25 @@ def validate_address(cls, value: str, info: ValidationInfo) -> str: raise ValueError(f"Invalid address provided for {info.field_name}") return add_leading_zeros(value) + @field_validator("amount", mode="before") + def validate_numeric_string(cls, value: str, info: ValidationInfo) -> Decimal: + """ + Validates that the provided amount is a valid hexadecimal string and converts it to a Decimal. + Args: + value (str): The amount string to validate. + info (ValidationInfo): Validation context information. + Raises: + ValueError: If the provided amount is not a valid hexadecimal number. + Returns: + Decimal: The validated and converted amount as a Decimal. + """ + try: + return Decimal(int(value, 16)) + except ValueError: + raise ValueError( + f"{info.field_name} field is not a valid hexadecimal number" + ) + class InterestRateModelEventData(BaseModel): """ @@ -100,6 +139,25 @@ def validate_address(cls, value: str, info: ValidationInfo) -> str: raise ValueError(f"Invalid address provided for {info.field_name}") return add_leading_zeros(value) + @field_validator("lending_index", "borrow_index", mode="before") + def validate_numeric_string(cls, value: str, info: ValidationInfo) -> Decimal: + """ + Validates that the provided amount is a valid hexadecimal string and converts it to a Decimal. + Args: + value (str): The amount string to validate. + info (ValidationInfo): Validation context information. + Raises: + ValueError: If the provided amount is not a valid hexadecimal number. + Returns: + Decimal: The validated and converted amount as a Decimal. + """ + try: + return Decimal(int(value, 16)) + except (ValueError, TypeError): + raise ValueError( + f"{info.field_name} field is not a valid hexadecimal number" + ) + class DebtTransferEventData(BaseModel): """ @@ -134,6 +192,25 @@ def validate_address(cls, value: str, info: ValidationInfo) -> str: raise ValueError(f"Invalid address provided for {info.field_name}") return add_leading_zeros(value) + @field_validator("amount", mode="before") + def validate_numeric_string(cls, value: str, info: ValidationInfo) -> Decimal: + """ + Validates that the provided amount is a valid hexadecimal string and converts it to a Decimal. + Args: + value (str): The amount string to validate. + info (ValidationInfo): Validation context information. + Raises: + ValueError: If the provided amount is not a valid hexadecimal number. + Returns: + Decimal: The validated and converted amount as a Decimal. + """ + try: + return Decimal(int(value, 16)) + except ValueError: + raise ValueError( + f"{info.field_name} field is not a valid hexadecimal number" + ) + class BearingCollateralMintEventData(BaseModel): """ @@ -166,6 +243,25 @@ def validate_address(cls, value: str, info: ValidationInfo) -> str: raise ValueError(f"Invalid address provided for {info.field_name}") return add_leading_zeros(value) + @field_validator("amount", mode="before") + def validate_numeric_string(cls, value: str, info: ValidationInfo) -> Decimal: + """ + Validates that the provided amount is a valid hexadecimal string and converts it to a Decimal. + Args: + value (str): The amount string to validate. + info (ValidationInfo): Validation context information. + Raises: + ValueError: If the provided amount is not a valid hexadecimal number. + Returns: + Decimal: The validated and converted amount as a Decimal. + """ + try: + return Decimal(int(value, 16)) + except ValueError: + raise ValueError( + f"{info.field_name} field is not a valid hexadecimal number" + ) + class BearingCollateralBurnEventData(BaseModel): """ @@ -197,25 +293,45 @@ def validate_address(cls, value: str, info: ValidationInfo) -> str: if not value.startswith("0x"): raise ValueError(f"Invalid address provided for {info.field_name}") return add_leading_zeros(value) - - + + @field_validator("amount", mode="before") + def validate_numeric_string(cls, value: str, info: ValidationInfo) -> Decimal: + """ + Validates that the provided amount is a valid hexadecimal string and converts it to a Decimal. + Args: + value (str): The amount string to validate. + info (ValidationInfo): Validation context information. + Raises: + ValueError: If the provided amount is not a valid hexadecimal number. + Returns: + Decimal: The validated and converted amount as a Decimal. + """ + try: + return Decimal(int(value, 16)) + except ValueError: + raise ValueError( + f"{info.field_name} field is not a valid hexadecimal number" + ) + + class NonInterestBearingCollateralMintEventData(BaseModel): """ Serializer for non-interest bearing collateral mint event data. - + Attributes: sender (str): The address of the sender recipient (str): The address of the recipient raw_amount (Decimal): The raw amount being transferred """ + sender: str recipient: str raw_amount: Decimal - + @field_validator("sender", "recipient") def validate_address(cls, value: str, info: ValidationInfo) -> str: """ - Validates that the provided address starts with '0x' and + Validates that the provided address starts with '0x' and formats it with leading zeros. Args: value (str): The address string to validate. @@ -227,8 +343,8 @@ def validate_address(cls, value: str, info: ValidationInfo) -> str: if not value.startswith("0x"): raise ValueError(f"Invalid address provided for {info.field_name}") return add_leading_zeros(value) - - @field_validator("raw_amount") + + @field_validator("raw_amount", mode="before") def validate_numeric_string(cls, value: str, info: ValidationInfo) -> Decimal: """ Validates that the provided amount is numeric and converts it to a Decimal. @@ -245,23 +361,24 @@ def validate_numeric_string(cls, value: str, info: ValidationInfo) -> Decimal: raise ValueError( f"{info.field_name} field is not a valid hexadecimal number" ) - + class NonInterestBearingCollateralBurnEventData(BaseModel): """ Serializer for non-interest bearing collateral burn event data. - + Attributes: user (str): The address of the user face_amount (Decimal): The face amount being burned """ + user: str face_amount: Decimal @field_validator("user") def validate_address(cls, value: str, info: ValidationInfo) -> str: """ - Validates that the provided address starts with '0x' and + Validates that the provided address starts with '0x' and formats it with leading zeros. Args: value (str): The address string to validate. @@ -273,8 +390,8 @@ def validate_address(cls, value: str, info: ValidationInfo) -> str: if not value.startswith("0x"): raise ValueError(f"Invalid address provided for {info.field_name}") return add_leading_zeros(value) - - @field_validator("face_amount") + + @field_validator("face_amount", mode="before") def validate_numeric_string(cls, value: str, info: ValidationInfo) -> Decimal: """ Validates that the provided amount is numeric and converts it to a Decimal. diff --git a/apps/data_handler/handlers/events/nostra/transform_events.py b/apps/data_handler/handlers/events/nostra/transform_events.py index 12bdbbe8..3b87c6c6 100644 --- a/apps/data_handler/handlers/events/nostra/transform_events.py +++ b/apps/data_handler/handlers/events/nostra/transform_events.py @@ -11,36 +11,31 @@ from data_handler.db.models.base import Base from data_handler.db.models.nostra_events import ( BearingCollateralBurnEventModel, + BearingCollateralMintEventModel, DebtBurnEventModel, DebtMintEventModel, DebtTransferEventModel, + InterestRateModelEventModel, + NonInterestBearingCollateralBurnEventModel, + NonInterestBearingCollateralMintEventModel, ) from data_handler.handler_tools.api_connector import DeRiskAPIConnector from data_handler.handler_tools.constants import ProtocolAddresses from data_handler.handler_tools.data_parser.nostra import NostraDataParser from data_handler.handler_tools.data_parser.serializers import ( BearingCollateralBurnEventData, + BearingCollateralMintEventData, DebtBurnEventData, DebtMintEventData, DebtTransferEventData, + InterestRateModelEventData, + NonInterestBearingCollateralBurnEventData, + NonInterestBearingCollateralMintEventData, ) from shared.constants import ProtocolIDs logger = logging.getLogger(__name__) -EVENT_MAPPING: Dict[str, Tuple[Callable, str]] = { - "DebtMint": (NostraDataParser.parse_debt_mint_event, "save_debt_mint_event"), - "DebtBurn": (NostraDataParser.parse_debt_burn_event, "save_debt_burn_event"), - "DebtTransfer": ( - NostraDataParser.parse_debt_transfer_event, - "save_debt_transfer_event", - ), - "BearingCollateralBurn": ( - NostraDataParser.parse_interest_bearing_collateral_burn_event, - "save_bearing_collateral_burn_event", - ), -} - class NostraTransformer: """ @@ -61,22 +56,38 @@ def __init__(self): self.last_block = self.db_connector.get_last_block(self.PROTOCOL_TYPE) self.data_parser = NostraDataParser() - self.EVENT_MAPPING: Dict[str, Tuple[Callable, str], Type[Base]] = { - "DebtMint": ( - self.data_parser.parse_debt_mint_event, - "save_debt_mint_event", + self.EVENT_MAPPING: Dict[str, Tuple[Callable, str]] = { + "BearingCollateralBurn": ( + self.data_parser.parse_interest_bearing_collateral_burn_event, + "save_bearing_collateral_burn_event", + ), + "BearingCollateralMint": ( + self.data_parser.parse_interest_bearing_collateral_mint_event, + "save_bearing_collateral_mint_event", ), "DebtBurn": ( self.data_parser.parse_debt_burn_event, "save_debt_burn_event", ), + "DebtMint": ( + self.data_parser.parse_debt_mint_event, + "save_debt_mint_event", + ), "DebtTransfer": ( self.data_parser.parse_debt_transfer_event, "save_debt_transfer_event", ), - "BearingCollateralBurn": ( - self.data_parser.parse_interest_bearing_collateral_burn_event, - "save_bearing_collateral_burn_event", + "InterestRateModel": ( + self.data_parser.parse_interest_rate_model_event, + "save_interest_rate_model_event", + ), + "NonInterestBearingCollateralBurn": ( + self.data_parser.parse_non_interest_bearing_collateral_burn_event, + "save_non_interest_bearing_collateral_burn_event", + ), + "NonInterestBearingCollateralMint": ( + self.data_parser.parse_non_interest_bearing_collateral_mint_event, + "save_non_interest_bearing_collateral_mint_event", ), } @@ -111,6 +122,50 @@ def fetch_and_transform_events( else: logger.info(f"Event type {event_type} not supported, yet...") + def save_bearing_collateral_burn_event( + self, + event_name: str, + block_number: int, + event_data: BearingCollateralBurnEventData, + ) -> None: + """ + Save a BearingCollateralBurn event to the database. + """ + self.db_connector.create_bearing_collateral_burn_event( + protocol_id=self.PROTOCOL_TYPE.value, + event_name=event_name, + block_number=block_number, + event_data={ + "user": event_data.user, + "amount": Decimal(event_data.amount), + }, + ) + logger.info( + f"Saved BearingCollateralBurn event: User={event_data.user}, Amount={event_data.amount}" + ) + + def save_bearing_collateral_mint_event( + self, + event_name: str, + block_number: int, + event_data: BearingCollateralMintEventData, + ) -> None: + """ + Save a BearingCollateralMint event to the database. + """ + self.db_connector.create_bearing_collateral_mint_event( + protocol_id=self.PROTOCOL_TYPE.value, + event_name=event_name, + block_number=block_number, + event_data={ + "user": event_data.user, + "amount": Decimal(event_data.amount), + }, + ) + logger.info( + f"Saved BearingCollateralMint event: User={event_data.user}, Amount={event_data.amount}" + ) + def save_debt_mint_event( self, event_name: str, block_number: int, event_data: DebtMintEventData ) -> None: @@ -169,26 +224,72 @@ def save_debt_transfer_event( f"Saved DebtTransfer event: Sender={event_data.sender}, Recipient={event_data.recipient}, Amount={event_data.amount}" ) - def save_bearing_collateral_burn_event( + def save_interest_rate_model_event( self, event_name: str, block_number: int, - event_data: BearingCollateralBurnEventData, + event_data: InterestRateModelEventData, ) -> None: """ - Save a BearingCollateralBurn event to the database. + Save an InterestRateModel event to the database. """ - self.db_connector.create_bearing_collateral_burn_event( + self.db_connector.create_interest_rate_model_event( + protocol_id=self.PROTOCOL_TYPE.value, + event_name=event_name, + block_number=block_number, + event_data={ + "debt_token": event_data.debt_token, + "lending_index": Decimal(event_data.lending_index), + "borrow_index": Decimal(event_data.borrow_index), + }, + ) + logger.info( + f"Saved InterestRateModel event: DebtToken={event_data.debt_token}, LendingIndex={event_data.lending_index}, BorrowIndex={event_data.borrow_index}" + ) + + def save_non_interest_bearing_collateral_burn_event( + self, + event_name: str, + block_number: int, + event_data: NonInterestBearingCollateralBurnEventData, + ) -> None: + """ + Save a NonInterestBearingCollateralBurn event to the database. + """ + self.db_connector.create_non_interest_bearing_collateral_burn_event( protocol_id=self.PROTOCOL_TYPE.value, event_name=event_name, block_number=block_number, event_data={ "user": event_data.user, - "amount": Decimal(event_data.amount), + "amount": Decimal(event_data.face_amount), }, ) logger.info( - f"Saved BearingCollateralBurn event: User={event_data.user}, Amount={event_data.amount}" + f"Saved NonInterestBearingCollateralBurn event: User={event_data.user}, Amount={event_data.face_amount}" + ) + + def save_non_interest_bearing_collateral_mint_event( + self, + event_name: str, + block_number: int, + event_data: NonInterestBearingCollateralMintEventData, + ) -> None: + """ + Save a NonInterestBearingCollateralMint event to the database. + """ + self.db_connector.create_non_interest_bearing_collateral_mint_event( + protocol_id=self.PROTOCOL_TYPE.value, + event_name=event_name, + block_number=block_number, + event_data={ + "sender": event_data.sender, + "recipient": event_data.recipient, + "amount": Decimal(event_data.raw_amount), + }, + ) + logger.info( + f"Saved NonInterestBearingCollateralMint event: Sender={event_data.sender}, Recipient={event_data.recipient}, Amount={event_data.raw_amount}" ) def run(self) -> None: diff --git a/apps/data_handler/tests/conftest.py b/apps/data_handler/tests/conftest.py index 6d528d76..7ca887d6 100644 --- a/apps/data_handler/tests/conftest.py +++ b/apps/data_handler/tests/conftest.py @@ -77,28 +77,25 @@ def mock_nostra_event_db_connector(): """ mock_nostra_event_db_connector = MagicMock(spec=NostraEventDBConnector) mock_nostra_event_db_connector.get_last_block.return_value = 0 - mock_nostra_event_db_connector.create_debt_mint_event = MagicMock() + mock_nostra_event_db_connector.create_bearing_collateral_burn_event = MagicMock() + mock_nostra_event_db_connector.create_bearing_collateral_mint_event = MagicMock() mock_nostra_event_db_connector.create_debt_burn_event = MagicMock() + mock_nostra_event_db_connector.create_debt_mint_event = MagicMock() mock_nostra_event_db_connector.create_debt_transfer_event = MagicMock() - mock_nostra_event_db_connector.create_bearing_collateral_burn_event = MagicMock() + mock_nostra_event_db_connector.create_interest_rate_model_event = MagicMock() + mock_nostra_event_db_connector.create_non_interest_bearing_collateral_burn_event = ( + MagicMock() + ) + mock_nostra_event_db_connector.create_non_interest_bearing_collateral_mint_event = ( + MagicMock() + ) yield mock_nostra_event_db_connector -@pytest.fixture(scope="function") -def mock_nostra_data_parser(): - """ - Mock for NostraDataParser - :return: None - """ - mock_nostra_data_parser = MagicMock(spec=NostraDataParser) - yield mock_nostra_data_parser - - @pytest.fixture(scope="function") def transformer( mock_api_connector, mock_nostra_event_db_connector, - mock_nostra_data_parser, ): """ Creates an instance of NostraTransformer with mocked dependencies. @@ -113,5 +110,5 @@ def transformer( transformer = NostraTransformer() transformer.api_connector = mock_api_connector transformer.db_connector = mock_nostra_event_db_connector - transformer.data_parser = mock_nostra_data_parser + transformer.data_parser = NostraDataParser() return transformer diff --git a/apps/data_handler/tests/test_nostra_transformer.py b/apps/data_handler/tests/test_nostra_transformer.py index 8daa49cd..ebdcf372 100644 --- a/apps/data_handler/tests/test_nostra_transformer.py +++ b/apps/data_handler/tests/test_nostra_transformer.py @@ -2,40 +2,65 @@ Test the nostra transformer """ -from decimal import Decimal from typing import Any, Dict from unittest.mock import MagicMock, patch import pytest -from data_handler.handler_tools.data_parser.serializers import ( +from data_handler.handler_tools.data_parser.serializers.nostra import ( BearingCollateralBurnEventData, + BearingCollateralMintEventData, DebtBurnEventData, DebtMintEventData, DebtTransferEventData, + InterestRateModelEventData, + NonInterestBearingCollateralBurnEventData, + NonInterestBearingCollateralMintEventData, ) from data_handler.handlers.events.nostra.transform_events import NostraTransformer from shared.constants import ProtocolIDs @pytest.fixture(scope="function") -def sample_debt_mint_event_data() -> Dict[str, Any]: +def sample_bearing_collateral_burn_event_data() -> Dict[str, Any]: """ - Sample debt mint event data + Sample bearing collateral burn event data """ return { - "id": "0x00a00637ed8fd6f3f83a1eb743a36c894c6fe5af5a87e8ab35697afb3422967e_3", - "block_hash": "0x07d1b221c40b6a19c0381d61ebbe8d048018b49314ae1bdc93938059e29febdf", - "block_number": 630008, - "transaction_hash": "0x00a00637ed8fd6f3f83a1eb743a36c894c6fe5af5a87e8ab35697afb3422967e", - "event_index": 3, + "id": "0x07abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd_0", + "block_hash": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "block_number": 630013, + "transaction_hash": "0x07abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "event_index": 0, "from_address": "0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05", - "keys": ["0xfa3f9acdb7b24dcf6d40d77ff2f87a87bca64a830a2169aebc9173db23ff41"], + "keys": ["0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd"], "data": [ - "0x1a0027d1bf86904d1051fe0ca94c39b659135f19504d663d66771a7424ca2eb", # user - "0x9184e72a000", # amount in hex + "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", # user + "0x2386f26fc10000", # amount in hex (0.1e18) ], - "timestamp": 1712276824, - "key_name": "DebtMint", + "timestamp": 1712278300, + "key_name": "BearingCollateralBurn", + } + + +@pytest.fixture(scope="function") +def sample_bearing_collateral_mint_event_data() -> Dict[str, Any]: + """ + Sample bearing collateral mint event data + """ + return { + "id": "0x08abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd_1", + "block_hash": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "block_number": 630015, + "transaction_hash": "0x08abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "event_index": 1, + "from_address": "0x05c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05", + "keys": ["0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd"], + "data": [ + "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", # user + "0x2386f26fc10000", # amount in hex (0.1e18) + ], + "timestamp": 1712278400, + "key_name": "BearingCollateralMint", } @@ -61,6 +86,28 @@ def sample_debt_burn_event_data() -> Dict[str, Any]: } +@pytest.fixture(scope="function") +def sample_debt_mint_event_data() -> Dict[str, Any]: + """ + Sample debt mint event data + """ + return { + "id": "0x00a00637ed8fd6f3f83a1eb743a36c894c6fe5af5a87e8ab35697afb3422967e_3", + "block_hash": "0x07d1b221c40b6a19c0381d61ebbe8d048018b49314ae1bdc93938059e29febdf", + "block_number": 630008, + "transaction_hash": "0x00a00637ed8fd6f3f83a1eb743a36c894c6fe5af5a87e8ab35697afb3422967e", + "event_index": 3, + "from_address": "0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05", + "keys": ["0xfa3f9acdb7b24dcf6d40d77ff2f87a87bca64a830a2169aebc9173db23ff41"], + "data": [ + "0x1a0027d1bf86904d1051fe0ca94c39b659135f19504d663d66771a7424ca2eb", # user + "0x9184e72a000", # amount in hex + ], + "timestamp": 1712276824, + "key_name": "DebtMint", + } + + @pytest.fixture(scope="function") def sample_debt_transfer_event_data() -> Dict[str, Any]: """ @@ -85,49 +132,126 @@ def sample_debt_transfer_event_data() -> Dict[str, Any]: @pytest.fixture(scope="function") -def sample_bearing_collateral_burn_event_data() -> Dict[str, Any]: +def sample_interest_rate_model_event_data() -> Dict[str, Any]: """ - Sample bearing collateral burn event data + Sample interest rate model event data """ return { - "id": "0x07abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd_0", + "id": "0x0babcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd_4", "block_hash": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", - "block_number": 630013, - "transaction_hash": "0x07abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", - "event_index": 0, - "from_address": "0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05", + "block_number": 630030, + "transaction_hash": "0x0babcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "event_index": 4, + "from_address": "0x08c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05", + "keys": ["0x33db1d611576200c90997bde1f948502469d333e65e87045c250e6efd2e42c7"], + "data": [ + "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", # debt_token + "0x2386f26fc10000", # lending_index + "0x2386f26fc10000", # borrow_index + ], + "timestamp": 1712278700, + "key_name": "InterestRateModel", + } + + +@pytest.fixture(scope="function") +def sample_non_interest_bearing_collateral_burn_event_data() -> Dict[str, Any]: + """ + Sample non-interest-bearing collateral burn event data + """ + return { + "id": "0x09abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd_2", + "block_hash": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "block_number": 630020, + "transaction_hash": "0x09abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "event_index": 2, + "from_address": "0x06c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05", "keys": ["0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd"], "data": [ "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", # user - "0x2386f26fc10000", # amount in hex (0.1e18) + "0x1bc16d674ec80000", # amount in hex (2e18) ], - "timestamp": 1712278300, - "key_name": "BearingCollateralBurn", + "timestamp": 1712278500, + "key_name": "NonInterestBearingCollateralBurn", } -def test_save_debt_mint_event(transformer, sample_debt_mint_event_data): +@pytest.fixture(scope="function") +def sample_non_interest_bearing_collateral_mint_event_data() -> Dict[str, Any]: """ - Test saving a debt mint event. + Sample non-interest-bearing collateral mint event data """ - # Setup API response - transformer.api_connector.get_data.return_value = [sample_debt_mint_event_data] + return { + "id": "0x0aabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd_3", + "block_hash": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "block_number": 630025, + "transaction_hash": "0x0aabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "event_index": 3, + "from_address": "0x07c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05", + "keys": ["0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd"], + "data": [ + "0xabcdef1234567890abcdef1234567890abcdef12", # sender + "0x1234567890abcdef1234567890abcdef12345678", # recipient + "0x1bc16d674ec80000", # amount in hex (2e18) + ], + "timestamp": 1712278600, + "key_name": "NonInterestBearingCollateralMint", + } - expected_parsed_data = DebtMintEventData( - user=sample_debt_mint_event_data["data"][0], - amount=Decimal(int(sample_debt_mint_event_data["data"][1], 16)), + +def test_save_bearing_collateral_burn_event( + transformer, sample_bearing_collateral_burn_event_data +): + """ + Test saving a bearing collateral burn event. + """ + transformer.api_connector.get_data.return_value = [ + sample_bearing_collateral_burn_event_data + ] + + expected_parsed_data = BearingCollateralBurnEventData( + user=sample_bearing_collateral_burn_event_data["data"][0], + amount=sample_bearing_collateral_burn_event_data["data"][1], ) - # Call the method transformer.fetch_and_transform_events( from_address=transformer.PROTOCOL_ADDRESSES, min_block=0, max_block=1000 ) - # Verify DB connector was called with correct data - transformer.db_connector.create_debt_mint_event.assert_called_once_with( + transformer.db_connector.create_bearing_collateral_burn_event.assert_called_once_with( protocol_id=ProtocolIDs.NOSTRA_ALPHA.value, - event_name=sample_debt_mint_event_data["key_name"], - block_number=sample_debt_mint_event_data["block_number"], + event_name=sample_bearing_collateral_burn_event_data["key_name"], + block_number=sample_bearing_collateral_burn_event_data["block_number"], + event_data={ + "user": expected_parsed_data.user, + "amount": expected_parsed_data.amount, + }, + ) + + +def test_save_bearing_collateral_mint_event( + transformer, sample_bearing_collateral_mint_event_data +): + """ + Test saving a bearing collateral mint event. + """ + transformer.api_connector.get_data.return_value = [ + sample_bearing_collateral_mint_event_data + ] + + expected_parsed_data = BearingCollateralMintEventData( + user=sample_bearing_collateral_mint_event_data["data"][0], + amount=sample_bearing_collateral_mint_event_data["data"][1], + ) + + transformer.fetch_and_transform_events( + from_address=transformer.PROTOCOL_ADDRESSES, min_block=0, max_block=1000 + ) + + transformer.db_connector.create_bearing_collateral_mint_event.assert_called_once_with( + protocol_id=ProtocolIDs.NOSTRA_ALPHA.value, + event_name=sample_bearing_collateral_mint_event_data["key_name"], + block_number=sample_bearing_collateral_mint_event_data["block_number"], event_data={ "user": expected_parsed_data.user, "amount": expected_parsed_data.amount, @@ -143,7 +267,7 @@ def test_save_debt_burn_event(transformer, sample_debt_burn_event_data): expected_parsed_data = DebtBurnEventData( user=sample_debt_burn_event_data["data"][0], - amount=Decimal(int(sample_debt_burn_event_data["data"][1], 16)), + amount=sample_debt_burn_event_data["data"][1], ) transformer.fetch_and_transform_events( @@ -161,6 +285,35 @@ def test_save_debt_burn_event(transformer, sample_debt_burn_event_data): ) +def test_save_debt_mint_event(transformer, sample_debt_mint_event_data): + """ + Test saving a debt mint event. + """ + # Setup API response + transformer.api_connector.get_data.return_value = [sample_debt_mint_event_data] + + expected_parsed_data = DebtMintEventData( + user=sample_debt_mint_event_data["data"][0], + amount=sample_debt_mint_event_data["data"][1], + ) + + # Call the method + transformer.fetch_and_transform_events( + from_address=transformer.PROTOCOL_ADDRESSES, min_block=0, max_block=1000 + ) + + # Verify DB connector was called with correct data + transformer.db_connector.create_debt_mint_event.assert_called_once_with( + protocol_id=ProtocolIDs.NOSTRA_ALPHA.value, + event_name=sample_debt_mint_event_data["key_name"], + block_number=sample_debt_mint_event_data["block_number"], + event_data={ + "user": expected_parsed_data.user, + "amount": expected_parsed_data.amount, + }, + ) + + def test_save_debt_transfer_event(transformer, sample_debt_transfer_event_data): """ Test saving a debt transfer event. @@ -170,7 +323,7 @@ def test_save_debt_transfer_event(transformer, sample_debt_transfer_event_data): expected_parsed_data = DebtTransferEventData( sender=sample_debt_transfer_event_data["data"][0], recipient=sample_debt_transfer_event_data["data"][1], - amount=Decimal(int(sample_debt_transfer_event_data["data"][2], 16)), + amount=sample_debt_transfer_event_data["data"][2], ) transformer.fetch_and_transform_events( @@ -189,32 +342,100 @@ def test_save_debt_transfer_event(transformer, sample_debt_transfer_event_data): ) -def test_save_bearing_collateral_burn_event( - transformer, sample_bearing_collateral_burn_event_data +def test_save_interest_rate_model_event( + transformer, sample_interest_rate_model_event_data ): """ - Test saving a bearing collateral burn event. + Test saving an interest rate model event. """ transformer.api_connector.get_data.return_value = [ - sample_bearing_collateral_burn_event_data + sample_interest_rate_model_event_data ] - expected_parsed_data = BearingCollateralBurnEventData( - user=sample_bearing_collateral_burn_event_data["data"][0], - amount=Decimal(int(sample_bearing_collateral_burn_event_data["data"][1], 16)), + expected_parsed_data = InterestRateModelEventData( + debt_token=sample_interest_rate_model_event_data["data"][0], + lending_index=sample_interest_rate_model_event_data["data"][1], + borrow_index=sample_interest_rate_model_event_data["data"][2], ) transformer.fetch_and_transform_events( from_address=transformer.PROTOCOL_ADDRESSES, min_block=0, max_block=1000 ) - transformer.db_connector.create_bearing_collateral_burn_event.assert_called_once_with( + transformer.db_connector.create_interest_rate_model_event.assert_called_once_with( protocol_id=ProtocolIDs.NOSTRA_ALPHA.value, - event_name=sample_bearing_collateral_burn_event_data["key_name"], - block_number=sample_bearing_collateral_burn_event_data["block_number"], + event_name=sample_interest_rate_model_event_data["key_name"], + block_number=sample_interest_rate_model_event_data["block_number"], + event_data={ + "debt_token": expected_parsed_data.debt_token, + "lending_index": expected_parsed_data.lending_index, + "borrow_index": expected_parsed_data.borrow_index, + }, + ) + + +def test_save_non_interest_bearing_collateral_burn_event( + transformer, sample_non_interest_bearing_collateral_burn_event_data +): + """ + Test saving a non-interest-bearing collateral burn event. + """ + transformer.api_connector.get_data.return_value = [ + sample_non_interest_bearing_collateral_burn_event_data + ] + + expected_parsed_data = NonInterestBearingCollateralBurnEventData( + user=sample_non_interest_bearing_collateral_burn_event_data["data"][0], + face_amount=sample_non_interest_bearing_collateral_burn_event_data["data"][1], + ) + + transformer.fetch_and_transform_events( + from_address=transformer.PROTOCOL_ADDRESSES, min_block=0, max_block=1000 + ) + + transformer.db_connector.create_non_interest_bearing_collateral_burn_event.assert_called_once_with( + protocol_id=ProtocolIDs.NOSTRA_ALPHA.value, + event_name=sample_non_interest_bearing_collateral_burn_event_data["key_name"], + block_number=sample_non_interest_bearing_collateral_burn_event_data[ + "block_number" + ], event_data={ "user": expected_parsed_data.user, - "amount": expected_parsed_data.amount, + "amount": expected_parsed_data.face_amount, + }, + ) + + +def test_save_non_interest_bearing_collateral_mint_event( + transformer, sample_non_interest_bearing_collateral_mint_event_data +): + """ + Test saving a non-interest-bearing collateral mint event. + """ + transformer.api_connector.get_data.return_value = [ + sample_non_interest_bearing_collateral_mint_event_data + ] + + expected_parsed_data = NonInterestBearingCollateralMintEventData( + sender=sample_non_interest_bearing_collateral_mint_event_data["data"][0], + recipient=sample_non_interest_bearing_collateral_mint_event_data["data"][1], + raw_amount=sample_non_interest_bearing_collateral_mint_event_data["data"][2], + ) + + transformer.fetch_and_transform_events( + from_address=transformer.PROTOCOL_ADDRESSES, min_block=0, max_block=1000 + ) + + transformer.db_connector.create_non_interest_bearing_collateral_mint_event.assert_called_once_with( + protocol_id=ProtocolIDs.NOSTRA_ALPHA.value, + event_name=sample_non_interest_bearing_collateral_mint_event_data["key_name"], + block_number=sample_non_interest_bearing_collateral_mint_event_data[ + "block_number" + ], + event_data={ + "sender": expected_parsed_data.sender, + "recipient": expected_parsed_data.recipient, + "amount": expected_parsed_data.raw_amount, }, ) @@ -236,10 +457,14 @@ def test_unsupported_event_type(transformer): ) # Verify no DB calls were made for Nostra events - transformer.db_connector.create_debt_mint_event.assert_not_called() + transformer.db_connector.create_bearing_collateral_burn_event.assert_not_called() + transformer.db_connector.create_bearing_collateral_mint_event.assert_not_called() transformer.db_connector.create_debt_burn_event.assert_not_called() + transformer.db_connector.create_debt_mint_event.assert_not_called() transformer.db_connector.create_debt_transfer_event.assert_not_called() - transformer.db_connector.create_bearing_collateral_burn_event.assert_not_called() + transformer.db_connector.create_interest_rate_model_event.assert_not_called() + transformer.db_connector.create_non_interest_bearing_collateral_burn_event.assert_not_called() + transformer.db_connector.create_non_interest_bearing_collateral_mint_event.assert_not_called() def test_api_error_handling(transformer):