|
7 | 7 | from backend.state import BackendRequest
|
8 | 8 |
|
9 | 9 | # Drift imports - adjust paths/names if necessary based on project structure
|
| 10 | +from driftpy.drift_client import DriftClient # For interacting with the Drift program |
| 11 | +from driftpy.addresses import get_high_leverage_mode_config_public_key # To get the PDA for config |
10 | 12 | from driftpy.drift_user import DriftUser
|
11 | 13 | from driftpy.user_map.user_map import UserMap # Assuming UserMap is accessible
|
12 | 14 | from driftpy.types import PerpPosition, UserAccount, is_variant, OraclePriceData # Added OraclePriceData
|
@@ -60,106 +62,110 @@ class BootableUserDetails(BaseModel):
|
60 | 62 | async def get_high_leverage_stats(request: BackendRequest):
|
61 | 63 | """
|
62 | 64 | Provides statistics about high leverage usage on the Drift protocol.
|
63 |
| - - Total spots: Maximum users allowed in high leverage mode (hardcoded). |
| 65 | + - Total spots: Maximum users allowed in high leverage mode. |
64 | 66 | - Opted-in spots: Users currently opted into high leverage mode.
|
65 | 67 | - Available spots: Spots remaining for users to opt-in.
|
66 |
| - - Bootable spots: Users opted-in, inactive for a defined period, and potentially with low overall leverage. |
| 68 | + - Bootable spots: Users opted-in, inactive for a defined period. |
67 | 69 | """
|
68 | 70 |
|
69 |
| - total_spots = 400 # Hardcoded maximum number of spots |
70 |
| - |
71 |
| - opted_in_users_count = 0 |
| 71 | + # Initialize with fallback values |
| 72 | + total_spots = 100 # Fallback, will be updated from on-chain if possible |
| 73 | + opted_in_users_count = 0 # Fallback, will be updated from on-chain if possible |
72 | 74 | bootable_count = 0
|
73 | 75 |
|
74 |
| - current_slot = getattr(request.state.backend_state, 'last_oracle_slot', 0) |
75 |
| - if current_slot == 0: |
76 |
| - logger.warning("Could not retrieve current_slot (last_oracle_slot) from backend state for /stats. Bootable check might be inaccurate.") |
77 |
| - # If current_slot is critical for bootable check, might return error or default. |
78 |
| - # For now, proceeding will mean inactivity check can't be reliably performed if current_slot is 0. |
79 |
| - |
80 |
| - user_map: Optional[UserMap] = getattr(request.state.backend_state, 'user_map', None) |
81 |
| - logger.info(f"UserMap object type from state: {type(user_map)}") |
82 |
| - |
83 |
| - if not user_map or not hasattr(user_map, 'values'): |
84 |
| - logger.warning("UserMap not found or invalid in backend state. Returning default stats.") |
85 |
| - return HighLeverageStats( |
86 |
| - slot=current_slot, |
87 |
| - total_spots=total_spots, |
88 |
| - available_spots=total_spots, |
89 |
| - bootable_spots=0, |
90 |
| - opted_in_spots=0 |
91 |
| - ) |
92 |
| - |
93 |
| - try: |
94 |
| - user_values = list(user_map.values()) |
95 |
| - logger.info(f"Processing {len(user_values)} users from UserMap for /stats.") |
96 |
| - |
97 |
| - if not user_values: |
98 |
| - logger.info("UserMap is empty for /stats.") |
99 |
| - # else: # Logging first user type can be verbose, let's assume it's DriftUser by now |
100 |
| - # logger.info(f"First user object type for /stats: {type(user_values[0])}") |
101 |
| - |
102 |
| - except Exception as e: |
103 |
| - logger.error(f"Error getting users from UserMap for /stats: {e}", exc_info=True) |
104 |
| - return HighLeverageStats( |
105 |
| - slot=current_slot, |
106 |
| - total_spots=total_spots, |
107 |
| - available_spots=total_spots, |
108 |
| - bootable_spots=0, |
109 |
| - opted_in_spots=0 |
110 |
| - ) |
| 76 | + current_slot = getattr(request.state.backend_state, 'last_oracle_slot', 0) |
| 77 | + config_account = None # To track if on-chain data was fetched |
111 | 78 |
|
112 |
| - for user in user_values: |
113 |
| - if not isinstance(user, DriftUser): |
114 |
| - logger.warning(f"Skipping item in user_map values for /stats, expected DriftUser, got {type(user)}") |
115 |
| - continue |
| 79 | + # Fetching On-Chain Data for total_spots and opted_in_users_count |
| 80 | + drift_client: Optional[DriftClient] = getattr(request.state.backend_state, 'dc', None) |
116 | 81 |
|
117 |
| - is_high_leverage = False |
118 |
| - user_account: Optional[UserAccount] = None # Define here for broader scope |
| 82 | + if drift_client: |
119 | 83 | try:
|
120 |
| - is_high_leverage = user.is_high_leverage_mode() |
121 |
| - if is_high_leverage: |
122 |
| - user_account = user.get_user_account() |
| 84 | + high_leverage_mode_config_pda = get_high_leverage_mode_config_public_key( |
| 85 | + drift_client.program_id |
| 86 | + ) |
| 87 | + config_account = await drift_client.program.account["HighLeverageModeConfig"].fetch( |
| 88 | + high_leverage_mode_config_pda |
| 89 | + ) |
| 90 | + total_spots = config_account.max_users |
| 91 | + opted_in_users_count = config_account.current_users |
| 92 | + logger.info(f"Successfully fetched HighLeverageModeConfig: max_users={total_spots}, current_users={opted_in_users_count}") |
123 | 93 | except Exception as e:
|
124 |
| - logger.error(f"Error checking high leverage status or getting account for user {user.user_public_key} in /stats: {e}", exc_info=True) |
125 |
| - continue |
| 94 | + logger.error(f"Error fetching HighLeverageModeConfig from on-chain: {e}. Using fallback values for total and opted-in spots.", exc_info=True) |
| 95 | + # Fallback values initialized earlier will be used. |
| 96 | + else: |
| 97 | + logger.warning("DriftClient (as 'dc') not found in backend state. Using fallback values for total and opted-in spots.") |
| 98 | + # Fallback values initialized earlier will be used. |
126 | 99 |
|
127 |
| - if is_high_leverage and user_account: |
128 |
| - opted_in_users_count += 1 |
129 |
| - |
130 |
| - # Check for bootable status based on inactivity |
131 |
| - is_inactive = False |
132 |
| - if current_slot > 0: # Ensure current_slot is valid before checking inactivity |
133 |
| - try: |
134 |
| - last_active_slot = user_account.last_active_slot # This is a int/BN |
135 |
| - # Ensure last_active_slot can be converted to int if it's a BN or similar type |
136 |
| - last_active_slot_int = int(str(last_active_slot)) |
137 |
| - if (current_slot - last_active_slot_int) > SLOT_INACTIVITY_THRESHOLD: |
138 |
| - is_inactive = True |
139 |
| - logger.debug(f"User {user.user_public_key} is inactive. Current: {current_slot}, Last Active: {last_active_slot_int}, Diff: {current_slot - last_active_slot_int}") |
140 |
| - except Exception as slot_check_e: |
141 |
| - logger.error(f"Error checking inactivity for user {user.user_public_key}: {slot_check_e}", exc_info=True) |
142 |
| - # Decide behavior: treat as not inactive, or skip bootable check for this user |
143 |
| - |
144 |
| - # The bot script uses inactivity and a general low leverage threshold. |
145 |
| - # For simplicity and alignment with bot, we use inactivity as the primary signal. |
146 |
| - # A stricter check could verify no *significant* positions or overall low leverage. |
147 |
| - if is_inactive: |
148 |
| - # Optionally, add the leverage check here if desired for stricter booting criteria: |
149 |
| - # current_leverage_ui = user.get_leverage() / MARGIN_PRECISION |
150 |
| - # if current_leverage_ui < BOOT_LEVERAGE_THRESHOLD: |
151 |
| - # bootable_count += 1 |
152 |
| - bootable_count += 1 |
| 100 | + # Calculating bootable_count (UserMap Iteration) |
| 101 | + user_map: Optional[UserMap] = getattr(request.state.backend_state, 'user_map', None) |
153 | 102 |
|
| 103 | + if not user_map or not hasattr(user_map, 'values'): |
| 104 | + logger.warning("UserMap not found or invalid in backend state. Bootable spots count will be 0.") |
| 105 | + # opted_in_users_count is already set (from on-chain or fallback) |
| 106 | + # bootable_count remains 0 |
| 107 | + else: |
| 108 | + if current_slot == 0: |
| 109 | + logger.warning("Could not retrieve current_slot (last_oracle_slot) from backend state for bootable check. Bootable count might be inaccurate (0).") |
| 110 | + # bootable_count remains 0 if current_slot is unavailable for inactivity check |
| 111 | + else: |
| 112 | + try: |
| 113 | + user_values = list(user_map.values()) |
| 114 | + logger.info(f"Processing {len(user_values)} users from UserMap for bootable status calculation.") |
| 115 | + |
| 116 | + for user in user_values: |
| 117 | + if not isinstance(user, DriftUser): |
| 118 | + logger.warning(f"Skipping item in user_map values for bootable calc, expected DriftUser, got {type(user)}") |
| 119 | + continue |
| 120 | + |
| 121 | + is_high_leverage = False |
| 122 | + user_account: Optional[UserAccount] = None |
| 123 | + try: |
| 124 | + is_high_leverage = user.is_high_leverage_mode() |
| 125 | + if is_high_leverage: # Only proceed if user is in high leverage mode |
| 126 | + user_account = user.get_user_account() |
| 127 | + except Exception as e: |
| 128 | + logger.error(f"Error checking high leverage status or getting account for user {user.user_public_key} in bootable calc: {e}", exc_info=True) |
| 129 | + continue |
| 130 | + |
| 131 | + if is_high_leverage and user_account: |
| 132 | + # DO NOT increment opted_in_users_count here. |
| 133 | + |
| 134 | + is_inactive = False |
| 135 | + # current_slot validity is already checked before this loop |
| 136 | + try: |
| 137 | + last_active_slot = user_account.last_active_slot |
| 138 | + last_active_slot_int = int(str(last_active_slot)) |
| 139 | + if (current_slot - last_active_slot_int) > SLOT_INACTIVITY_THRESHOLD: |
| 140 | + is_inactive = True |
| 141 | + logger.debug(f"User {user.user_public_key} is inactive for bootable check. Current: {current_slot}, Last Active: {last_active_slot_int}, Diff: {current_slot - last_active_slot_int}") |
| 142 | + except Exception as slot_check_e: |
| 143 | + logger.error(f"Error checking inactivity for user {user.user_public_key} (bootable calc): {slot_check_e}", exc_info=True) |
| 144 | + |
| 145 | + if is_inactive: |
| 146 | + bootable_count += 1 |
| 147 | + except Exception as e: |
| 148 | + logger.error(f"Error getting or processing users from UserMap for bootable calculation: {e}", exc_info=True) |
| 149 | + # bootable_count remains as it was before the try block or 0 if it's the first error. |
| 150 | + |
| 151 | + # Calculating available_spots |
154 | 152 | available_spots = total_spots - opted_in_users_count
|
155 |
| - logger.info(f"Calculated Stats for /stats: Slot={current_slot}, Total={total_spots}, OptedIn={opted_in_users_count}, Available={available_spots}, Bootable={bootable_count}") |
| 153 | + available_spots = max(0, available_spots) # Ensure not negative |
| 154 | + |
| 155 | + logger.info( |
| 156 | + f"Calculated Stats for /stats: Slot={current_slot}, " |
| 157 | + f"Total={total_spots} (Source: {'On-chain' if config_account else 'Fallback'}), " |
| 158 | + f"OptedIn={opted_in_users_count} (Source: {'On-chain' if config_account else 'Fallback'}), " |
| 159 | + f"Available={available_spots}, " |
| 160 | + f"Bootable={bootable_count} (Source: UserMap)" |
| 161 | + ) |
156 | 162 |
|
157 | 163 | return HighLeverageStats(
|
158 | 164 | slot=current_slot,
|
159 | 165 | total_spots=total_spots,
|
160 | 166 | available_spots=available_spots,
|
161 | 167 | bootable_spots=bootable_count,
|
162 |
| - opted_in_spots=opted_in_users_count |
| 168 | + opted_in_spots=opted_in_users_count # This matches the model field name |
163 | 169 | )
|
164 | 170 |
|
165 | 171 | @router.get("/positions/detailed", response_model=List[HighLeveragePositionDetail])
|
@@ -194,6 +200,9 @@ async def get_high_leverage_positions_detailed(request: BackendRequest):
|
194 | 200 |
|
195 | 201 | try:
|
196 | 202 | if user.is_high_leverage_mode():
|
| 203 | + high_leverage_count = getattr(request.state.backend_state, 'high_leverage_count', 0) + 1 |
| 204 | + setattr(request.state.backend_state, 'high_leverage_count', high_leverage_count) |
| 205 | + logger.info(f"Found high leverage mode user: {user.user_public_key} (Total: {high_leverage_count})") |
197 | 206 | user_account: UserAccount = user.get_user_account()
|
198 | 207 | user_public_key_str = str(user.user_public_key)
|
199 | 208 | authority_str = str(user_account.authority)
|
|
0 commit comments