From 6fd194dcf612981572ed2cd02ddaccc4c649b948 Mon Sep 17 00:00:00 2001 From: Anton Rudenko Date: Wed, 8 Jan 2025 22:07:00 +0400 Subject: [PATCH] fix: Normalize the values by dividing by 1e27 --- .../handlers/loan_states/zklend/events.py | 169 +++++++++++------- 1 file changed, 101 insertions(+), 68 deletions(-) diff --git a/apps/data_handler/handlers/loan_states/zklend/events.py b/apps/data_handler/handlers/loan_states/zklend/events.py index c24f2938..e7836dfe 100644 --- a/apps/data_handler/handlers/loan_states/zklend/events.py +++ b/apps/data_handler/handlers/loan_states/zklend/events.py @@ -18,22 +18,21 @@ from typing import Optional import pandas as pd - from data_handler.db.crud import InitializerDBConnector from data_handler.handler_tools.data_parser.zklend import ZklendDataParser from data_handler.handlers.helpers import get_async_symbol +from data_handler.handlers.loan_states.zklend.settings import ( + ZKLEND_SPECIFIC_TOKEN_SETTINGS, +) from data_handler.handlers.settings import TokenSettings from shared.helpers import add_leading_zeros from shared.loan_entity import LoanEntity from shared.state import State -from data_handler.handlers.loan_states.zklend.settings import ( - ZKLEND_SPECIFIC_TOKEN_SETTINGS, -) logger = logging.getLogger(__name__) from data_handler.handlers import blockchain_call - +from shared.constants import ProtocolIDs from shared.types import ( InterestRateModels, Portfolio, @@ -44,9 +43,10 @@ ZkLendCollateralTokenParameters, ZkLendDebtTokenParameters, ) -from shared.constants import ProtocolIDs -ZKLEND_MARKET: str = ("0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05") +ZKLEND_MARKET: str = ( + "0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05" +) EVENTS_METHODS_MAPPING: dict[str, str] = { "AccumulatorsSync": "process_accumulators_sync_event", "zklend::market::Market::AccumulatorsSync": "process_accumulators_sync_event", @@ -71,15 +71,15 @@ class ZkLendLoanEntity(LoanEntity): """ - A class that describes the zkLend loan entity. On top of the abstract `LoanEntity`, + A class that describes the zkLend loan entity. On top of the abstract `LoanEntity`, it implements the `deposit` and - `collateral_enabled` attributes in order to help with accounting for the changes in + `collateral_enabled` attributes in order to help with accounting for the changes in collateral. This is because under zkLend, collateral is the amount deposited that is specificaly flagged with `collateral_enabled` set to True - for the given token. To properly account for the changes in collateral, we must hold the + for the given token. To properly account for the changes in collateral, we must hold the information about the - given token's deposits being enabled as collateral or not and the amount of the deposits. + given token's deposits being enabled as collateral or not and the amount of the deposits. We keep all balances in raw amounts. """ @@ -146,10 +146,15 @@ def compute_debt_to_be_liquidated( # TODO: Commit a PDF with the derivation of the formula? numerator = debt_usd - risk_adjusted_collateral_usd denominator = prices[debt_token_underlying_address] * ( - 1 - collateral_token_parameters[collateral_token_underlying_address].collateral_factor * - ( - 1 + - collateral_token_parameters[collateral_token_underlying_address].liquidation_bonus + 1 + - collateral_token_parameters[ + collateral_token_underlying_address + ].collateral_factor + * ( + 1 + + collateral_token_parameters[ + collateral_token_underlying_address + ].liquidation_bonus ) ) max_debt_to_be_liquidated = numerator / denominator @@ -162,7 +167,7 @@ def compute_debt_to_be_liquidated( class ZkLendState(State): """ - A class that describes the state + A class that describes the state of all zkLend loan entities. It implements methods for correct processing of every relevant event. """ @@ -181,25 +186,32 @@ def __init__( self.db_connector = InitializerDBConnector() def process_accumulators_sync_event(self, event: pd.Series) -> None: - """Processes an accumulators sync event, updating collateral and + """Processes an accumulators sync event, updating collateral and debt interest rate models based on the latest data.""" - # The order of the values in the `data` column is: `token`, + # The order of the values in the `data` column is: `token`, # `lending_accumulator`, `debt_accumulator`. - # Example: + # Example: # https://starkscan.co/event/0x029628b89875a98c1c64ae206e7eb65669cb478a24449f3485f5e98aba6204dc_0. # TODO: Integrate the ZEND token once it's allowed to be borrowed or used as collateral. - parsed_event_data = ZklendDataParser.parse_accumulators_sync_event(event["data"]) + parsed_event_data = ZklendDataParser.parse_accumulators_sync_event( + event["data"] + ) token = add_leading_zeros(parsed_event_data.token) - collateral_interest_rate_index = parsed_event_data.lending_accumulator - debt_interest_rate_index = parsed_event_data.debt_accumulator + # Normalize the values by dividing by 1e27 + collateral_interest_rate_index = ( + parsed_event_data.lending_accumulator / decimal.Decimal("1e27") + ) + debt_interest_rate_index = parsed_event_data.debt_accumulator / decimal.Decimal( + "1e27" + ) self.interest_rate_models.collateral[token] = collateral_interest_rate_index self.interest_rate_models.debt[token] = debt_interest_rate_index def process_deposit_event(self, event: pd.Series) -> None: """Handles a deposit event, increasing the user's - deposit and optionally setting it as collateral.""" + deposit and optionally setting it as collateral.""" # The order of the values in the `data` column is: `user`, `token`, `face_amount`. # Example: # https://starkscan.co/event/0x036185142bb51e2c1f5bfdb1e6cef81f8ea87fd4d777990014249bf5435fd31b_3. @@ -215,7 +227,9 @@ def process_deposit_event(self, event: pd.Series) -> None: self.loan_entities[user].deposit.increase_value(token=token, value=raw_amount) if self.loan_entities[user].collateral_enabled[token]: - self.loan_entities[user].collateral.increase_value(token=token, value=raw_amount) + self.loan_entities[user].collateral.increase_value( + token=token, value=raw_amount + ) if user == self.verbose_user: logging.info( "In block number = {}, raw amount = {} of token = {} was deposited.".format( @@ -226,7 +240,7 @@ def process_deposit_event(self, event: pd.Series) -> None: ) def process_collateral_enabled_event(self, event: pd.Series) -> None: - """Processes a collateral enablement event, + """Processes a collateral enablement event, activating the specified token as collateral for the user.""" # The order of the values in the `data` column is: `user`, `token`. # Example: @@ -259,7 +273,7 @@ def process_collateral_enabled_event(self, event: pd.Series) -> None: ) def process_collateral_disabled_event(self, event: pd.Series) -> None: - """Processes a collateral disablement event, + """Processes a collateral disablement event, setting collateral for the specified token to zero for the user.""" # The order of the values in the `data` column is: `user`, `token`. # Example: @@ -272,7 +286,9 @@ def process_collateral_disabled_event(self, event: pd.Series) -> None: self.loan_entities[user].extra_info.timestamp = event["timestamp"] self.loan_entities[user].collateral_enabled[token] = False - self.loan_entities[user].collateral.set_value(token=token, value=decimal.Decimal("0")) + self.loan_entities[user].collateral.set_value( + token=token, value=decimal.Decimal("0") + ) if user == self.verbose_user: logging.info( "In block number = {}, collateral was disabled for token = {}.".format( @@ -282,8 +298,9 @@ def process_collateral_disabled_event(self, event: pd.Series) -> None: ) def process_withdrawal_event(self, event: pd.Series) -> None: - """Handles a withdrawal event by reducing - the user's deposit and collateral, adjusting based on the raw amount withdrawn.""" + """Handles a withdrawal event by reducing + the user's deposit and collateral, adjusting based on the raw amount withdrawn. + """ # The order of the values in the `data` column is: `user`, `token`, `face_amount`. # Example: # https://starkscan.co/event/0x03472cf7511687a55bc7247f8765c4bbd2c18b70e09b2a10a77c61f567bfd2cb_4. @@ -293,7 +310,8 @@ def process_withdrawal_event(self, event: pd.Series) -> None: # Calculate the raw amount from face amount raw_amount = ( - decimal.Decimal(str(data.amount)) / self.interest_rate_models.collateral[token] + decimal.Decimal(str(data.amount)) + / self.interest_rate_models.collateral[token] ) # Add additional info: block number and timestamp @@ -305,7 +323,9 @@ def process_withdrawal_event(self, event: pd.Series) -> None: self.loan_entities[user].deposit.increase_value(token=token, value=-raw_amount) if self.loan_entities[user].collateral_enabled[token]: - self.loan_entities[user].collateral.increase_value(token=token, value=-raw_amount) + self.loan_entities[user].collateral.increase_value( + token=token, value=-raw_amount + ) # Log the information if the user matches the verbose user if user == self.verbose_user: @@ -318,7 +338,7 @@ def process_withdrawal_event(self, event: pd.Series) -> None: ) def process_borrowing_event(self, event: pd.Series) -> None: - """Processes a borrowing event, increasing the user's debt by the + """Processes a borrowing event, increasing the user's debt by the raw amount borrowed for the specified token.""" # The order of the values in the `data` column is: `user`, # `token`, `raw_amount`, `face_amount`. @@ -342,7 +362,7 @@ def process_borrowing_event(self, event: pd.Series) -> None: ) def process_repayment_event(self, event: pd.Series) -> None: - """Processes a repayment event, updating the user’s debt by + """Processes a repayment event, updating the user’s debt by reducing it according to the raw amount repaid.""" data = ZklendDataParser.parse_repayment_event(event["data"]) @@ -365,7 +385,7 @@ def process_repayment_event(self, event: pd.Series) -> None: ) def process_liquidation_event(self, event: pd.Series) -> None: - """Processes a liquidation event, adjusting the user's debt and collateral + """Processes a liquidation event, adjusting the user's debt and collateral values based on liquidation amounts.""" # The order of the arguments is: `liquidator`, `user`, `debt_token`, # `debt_raw_amount`, `debt_face_amount`, @@ -377,7 +397,8 @@ def process_liquidation_event(self, event: pd.Series) -> None: user = data.user collateral_raw_amount = ( - data.collateral_amount / self.interest_rate_models.collateral[data.collateral_token] + data.collateral_amount + / self.interest_rate_models.collateral[data.collateral_token] ) # add additional info block and timestamp self.loan_entities[user].extra_info.block = event["block_number"] @@ -422,7 +443,10 @@ def compute_liquidable_debt_at_price( for token, token_amount in loan_entity.collateral.items() if token_amount > decimal.Decimal("0") } - if (not collateral_token_underlying_address in collateral_token_underlying_addresses): + if ( + not collateral_token_underlying_address + in collateral_token_underlying_addresses + ): continue # Filter out entities where the debt token of interest is borrowed. @@ -457,7 +481,7 @@ def compute_liquidable_debt_at_price( if health_factor >= 1.0 or health_factor <= 0.0: continue - # Find out how much of the `debt_token` will be liquidated. + # Find out how much of the `debt_token` will be liquidated. # We assume that the liquidator receives the # collateral token of interest even though it might not be the most # optimal choice for the liquidator. @@ -472,12 +496,19 @@ def compute_liquidable_debt_at_price( return max_liquidated_amount async def collect_token_parameters(self) -> None: - """Collects and sets token parameters for collateral and debt - tokens under zkLend, including collateral factors, liquidation bonuses, and debt factors.""" + """Collects and sets token parameters for collateral and debt + tokens under zkLend, including collateral factors, liquidation bonuses, and debt factors. + """ # Get the sets of unique collateral and debt tokens. - collateral_tokens = {y for x in self.loan_entities.values() for y in x.collateral.values.keys()} - debt_tokens = {y for x in self.loan_entities.values() for y in x.debt.values.keys()} - logging.info(f"Collecting token parameters for collateral tokens: {collateral_tokens}") + collateral_tokens = { + y for x in self.loan_entities.values() for y in x.collateral.values.keys() + } + debt_tokens = { + y for x in self.loan_entities.values() for y in x.debt.values.keys() + } + logging.info( + f"Collecting token parameters for collateral tokens: {collateral_tokens}" + ) logging.info(f"Collecting token parameters for debt tokens: {debt_tokens}") # Get parameters for each collateral and debt token. Under zkLend, # the collateral token in the events data is @@ -486,11 +517,11 @@ async def collect_token_parameters(self) -> None: underlying_collateral_token_symbol = await get_async_symbol( token_address=underlying_collateral_token_address ) - # The order of the arguments is: + # The order of the arguments is: # `enabled`, `decimals`, `z_token_address`, `interest_rate_model`, - # `collateral_factor`, `borrow_factor`, + # `collateral_factor`, `borrow_factor`, # `reserve_factor`, `last_update_timestamp`, `lending_accumulator`, - # `debt_accumulator`, `current_lending_rate`, `current_borrowing_rate`, + # `debt_accumulator`, `current_lending_rate`, `current_borrowing_rate`, # `raw_total_debt`, `flash_loan_fee`, # `liquidation_bonus`, `debt_limit`. reserve_data = await blockchain_call.func_call( @@ -499,27 +530,29 @@ async def collect_token_parameters(self) -> None: calldata=[underlying_collateral_token_address], ) collateral_token_address = add_leading_zeros(hex(reserve_data[2])) - collateral_token_symbol = await get_async_symbol(token_address=collateral_token_address) - self.token_parameters.collateral[underlying_collateral_token_address] = ( - ZkLendCollateralTokenParameters( - address=collateral_token_address, - decimals=int(reserve_data[1]), - symbol=collateral_token_symbol, - underlying_symbol=underlying_collateral_token_symbol, - underlying_address=underlying_collateral_token_address, - collateral_factor=reserve_data[4] / 1e27, - liquidation_bonus=reserve_data[14] / 1e27, - ) + collateral_token_symbol = await get_async_symbol( + token_address=collateral_token_address + ) + self.token_parameters.collateral[ + underlying_collateral_token_address + ] = ZkLendCollateralTokenParameters( + address=collateral_token_address, + decimals=int(reserve_data[1]), + symbol=collateral_token_symbol, + underlying_symbol=underlying_collateral_token_symbol, + underlying_address=underlying_collateral_token_address, + collateral_factor=reserve_data[4] / 1e27, + liquidation_bonus=reserve_data[14] / 1e27, ) for underlying_debt_token_address in debt_tokens: underlying_debt_token_symbol = await get_async_symbol( token_address=underlying_debt_token_address ) - # The order of the arguments is: `enabled`, `decimals`, + # The order of the arguments is: `enabled`, `decimals`, # `z_token_address`, `interest_rate_model`, - # `collateral_factor`, `borrow_factor`, `reserve_factor`, + # `collateral_factor`, `borrow_factor`, `reserve_factor`, # `last_update_timestamp`, `lending_accumulator`, - # `debt_accumulator`, `current_lending_rate`, `current_borrowing_rate`, + # `debt_accumulator`, `current_lending_rate`, `current_borrowing_rate`, # `raw_total_debt`, `flash_loan_fee`, # `liquidation_bonus`, `debt_limit`. reserve_data = await blockchain_call.func_call( @@ -529,13 +562,13 @@ async def collect_token_parameters(self) -> None: ) debt_token_address = add_leading_zeros(hex(reserve_data[2])) debt_token_symbol = await get_async_symbol(token_address=debt_token_address) - self.token_parameters.debt[underlying_debt_token_address] = ( - ZkLendDebtTokenParameters( - address=debt_token_address, - decimals=int(reserve_data[1]), - symbol=debt_token_symbol, - underlying_symbol=underlying_debt_token_symbol, - underlying_address=underlying_debt_token_address, - debt_factor=reserve_data[5] / 1e27, - ) + self.token_parameters.debt[ + underlying_debt_token_address + ] = ZkLendDebtTokenParameters( + address=debt_token_address, + decimals=int(reserve_data[1]), + symbol=debt_token_symbol, + underlying_symbol=underlying_debt_token_symbol, + underlying_address=underlying_debt_token_address, + debt_factor=reserve_data[5] / 1e27, )