Skip to content

Commit

Permalink
fix: Normalize the values by dividing by 1e27
Browse files Browse the repository at this point in the history
  • Loading branch information
MrRoudyk committed Jan 8, 2025
1 parent 8eae891 commit 6fd194d
Showing 1 changed file with 101 additions and 68 deletions.
169 changes: 101 additions & 68 deletions apps/data_handler/handlers/loan_states/zklend/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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",
Expand All @@ -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.
"""
Expand Down Expand Up @@ -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
Expand All @@ -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.
"""
Expand All @@ -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.
Expand All @@ -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(
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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(
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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`.
Expand All @@ -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"])

Expand All @@ -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`,
Expand All @@ -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"]
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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,
)

0 comments on commit 6fd194d

Please sign in to comment.