diff --git a/apps/data_handler/db/crud.py b/apps/data_handler/db/crud.py index 8a104301..d90c44ca 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, @@ -862,19 +866,20 @@ def apply_filters(query): 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() try: - event = DebtMintEventModel( + event = BearingCollateralBurnEventModel( protocol_id=protocol_id, event_name=event_name, block_number=block_number, @@ -883,10 +888,35 @@ def create_debt_mint_event( ) db.add(event) db.commit() - logger.info(f"DebtMint event saved: {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 + finally: + db.close() + + 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. + """ + db = self.Session() + try: + event = BearingCollateralMintEventModel( + 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"BearingCollateralMint event saved: {event}") + except SQLAlchemyError as e: + db.rollback() + logger.error(f"Error creating BearingCollateralMintEventModel: {e}") raise e finally: db.close() @@ -916,6 +946,31 @@ def create_debt_burn_event( 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. + """ + db = self.Session() + 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}") + except SQLAlchemyError as e: + db.rollback() + logger.error(f"Error creating DebtMintEventModel: {e}") + raise e + finally: + db.close() + def create_debt_transfer_event( self, protocol_id: str, event_name: str, block_number: int, event_data: dict ) -> None: @@ -942,15 +997,41 @@ def create_debt_transfer_event( 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() try: - event = BearingCollateralBurnEventModel( + 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"), + ) + db.add(event) + db.commit() + logger.info(f"InterestRateModel event saved: {event}") + except SQLAlchemyError as e: + db.rollback() + logger.error(f"Error creating InterestRateModelEventModel: {e}") + raise e + finally: + db.close() + + 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. + """ + db = self.Session() + try: + event = NonInterestBearingCollateralBurnEventModel( protocol_id=protocol_id, event_name=event_name, block_number=block_number, @@ -959,10 +1040,40 @@ def create_bearing_collateral_burn_event( ) db.add(event) db.commit() - logger.info(f"BearingCollateralBurn event saved: {event}") + logger.info(f"NonInterestBearingCollateralBurn event saved: {event}") except SQLAlchemyError as e: db.rollback() - logger.error(f"Error creating BearingCollateralBurnEventModel: {e}") + logger.error( + f"Error creating NonInterestBearingCollateralBurnEventModel: {e}" + ) + raise e + finally: + db.close() + + 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. + """ + db = self.Session() + try: + 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"), + ) + db.add(event) + db.commit() + logger.info(f"NonInterestBearingCollateralMint event saved: {event}") + except SQLAlchemyError as e: + db.rollback() + logger.error( + f"Error creating NonInterestBearingCollateralMintEventModel: {e}" + ) raise e finally: db.close() @@ -988,24 +1099,43 @@ def apply_filters(query, model): query = query.filter(model.block_number == block_number) return query - debt_mint_query = apply_filters( - db.query(DebtMintEventModel), DebtMintEventModel + bearing_collateral_burn_query = apply_filters( + db.query(BearingCollateralBurnEventModel), + BearingCollateralBurnEventModel, + ) + bearing_collateral_mint_query = apply_filters( + db.query(BearingCollateralMintEventModel), + BearingCollateralMintEventModel, ) debt_burn_query = apply_filters( db.query(DebtBurnEventModel), DebtBurnEventModel ) + debt_mint_query = apply_filters( + db.query(DebtMintEventModel), DebtMintEventModel + ) debt_transfer_query = apply_filters( db.query(DebtTransferEventModel), DebtTransferEventModel ) - bearing_collateral_burn_query = apply_filters( - db.query(BearingCollateralBurnEventModel), - BearingCollateralBurnEventModel, + interest_rate_model_query = apply_filters( + db.query(InterestRateModelEventModel), InterestRateModelEventModel + ) + non_interest_bearing_collateral_burn_query = apply_filters( + db.query(NonInterestBearingCollateralBurnEventModel), + NonInterestBearingCollateralBurnEventModel, + ) + non_interest_bearing_collateral_mint_query = apply_filters( + db.query(NonInterestBearingCollateralMintEventModel), + NonInterestBearingCollateralMintEventModel, ) - combined_query = debt_mint_query.union_all( + combined_query = interest_rate_model_query.union_all( + bearing_collateral_burn_query, + bearing_collateral_mint_query, debt_burn_query, + debt_mint_query, debt_transfer_query, - bearing_collateral_burn_query, + non_interest_bearing_collateral_burn_query, + non_interest_bearing_collateral_mint_query, ) events = combined_query.all() return events 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..7e84f4a1 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,15 @@ 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: + 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 +76,15 @@ 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: + 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 +119,15 @@ 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: + 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 +162,15 @@ 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: + 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 +203,15 @@ 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: + 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 +243,35 @@ 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: + 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 +283,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 +301,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 +330,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):