diff --git a/protocol-v2 b/protocol-v2 index a8ff7ee4..d7fa4068 160000 --- a/protocol-v2 +++ b/protocol-v2 @@ -1 +1 @@ -Subproject commit a8ff7ee4af6b961328d656cfa3dd4e6c5e56425b +Subproject commit d7fa4068456289299fcfa24e87a14f7456f1d814 diff --git a/src/driftpy/constants/numeric_constants.py b/src/driftpy/constants/numeric_constants.py index 92529455..6eccd01b 100644 --- a/src/driftpy/constants/numeric_constants.py +++ b/src/driftpy/constants/numeric_constants.py @@ -130,3 +130,8 @@ OPEN_ORDER_MARGIN_REQUIREMENT = QUOTE_PRECISION / 100 LIQUIDATION_PCT_PRECISION = TEN_THOUSAND + +FUEL_START_TS = 1722384000 +FUEL_WINDOW = 60 * 60 * 24 * 28 + +GOV_SPOT_MARKET_INDEX = 15 diff --git a/src/driftpy/drift_user.py b/src/driftpy/drift_user.py index d8ea5e73..ac3c0ce6 100644 --- a/src/driftpy/drift_user.py +++ b/src/driftpy/drift_user.py @@ -12,6 +12,11 @@ from driftpy.math.margin import * from driftpy.math.spot_balance import get_strict_token_value from driftpy.math.spot_market import * +from driftpy.math.fuel import ( + calculate_spot_fuel_bonus, + calculate_perp_fuel_bonus, + calculate_insurance_fuel_bonus, +) from driftpy.accounts.oracle import * from driftpy.math.spot_position import ( get_worst_case_token_amounts, @@ -1290,3 +1295,107 @@ def get_net_spot_market_value( ) return total_asset_value - total_liability_value + + def get_fuel_bonus( + self, now: int, include_settled: bool = True, include_unsettled: bool = True + ) -> dict[str, int]: + user_account = self.get_user_account() + total_fuel = { + "insurance_fuel": 0, + "taker_fuel": 0, + "maker_fuel": 0, + "deposit_fuel": 0, + "borrow_fuel": 0, + "position_fuel": 0, + } + + if include_settled: + user_stats = self.drift_client.get_user_stats().get_account() + total_fuel["taker_fuel"] += user_stats.fuel_taker + total_fuel["maker_fuel"] += user_stats.fuel_maker + total_fuel["deposit_fuel"] += user_stats.fuel_deposits + total_fuel["borrow_fuel"] += user_stats.fuel_borrows + total_fuel["position_fuel"] += user_stats.fuel_positions + + if include_unsettled: + # fuel bonus numerator is the time since the last fuel bonus update, capped at the start of the fuel program + fuel_bonus_numerator = max( + now - max(user_account.last_fuel_bonus_update_ts, FUEL_START_TS), 0 + ) + if fuel_bonus_numerator > 0: + for spot_position in self.get_active_spot_positions(): + spot_market_account = self.drift_client.get_spot_market_account( + spot_position.market_index + ) + token_amount = self.get_token_amount(spot_position.market_index) + oracle_price_data = self.get_oracle_data_for_spot_market( + spot_position.market_index + ) + twap_5min = calculate_live_oracle_twap( + spot_market_account.historical_oracle_data, + oracle_price_data, + now, + FIVE_MINUTE, + ) + strict_oracle_price = StrictOraclePrice( + oracle_price_data.price, twap_5min + ) + signed_token_value = get_strict_token_value( + token_amount, spot_market_account.decimals, strict_oracle_price + ) + spot_fuel = calculate_spot_fuel_bonus( + spot_market_account, signed_token_value, fuel_bonus_numerator + ) + if signed_token_value > 0: + total_fuel["deposit_fuel"] += spot_fuel + else: + total_fuel["borrow_fuel"] += spot_fuel + + for perp_position in self.get_active_perp_positions(): + oracle_price_data = self.get_oracle_data_for_perp_market( + perp_position.market_index + ) + perp_market_account = self.drift_client.get_perp_market_account( + perp_position.market_index + ) + base_asset_value = self.get_perp_position_value( + perp_position.market_index, oracle_price_data, False + ) + total_fuel["position_fuel"] += calculate_perp_fuel_bonus( + perp_market_account, base_asset_value, fuel_bonus_numerator + ) + + user_stats = self.drift_client.get_user_stats().get_account() + + if user_stats.if_staked_gov_token_amount > 0: + spot_market_account = self.drift_client.get_spot_market_account( + GOV_SPOT_MARKET_INDEX + ) + fuel_bonus_numerator_user_stats = ( + now - user_stats.last_fuel_bonus_update_ts + ) + total_fuel["insurance_fuel"] += calculate_insurance_fuel_bonus( + spot_market_account, + user_stats.if_staked_gov_token_amount, + fuel_bonus_numerator_user_stats, + ) + + return total_fuel + + def get_perp_position_value( + self, + market_index: int, + oracle_price_data: OraclePriceData, + include_open_orders: bool = False, + ): + perp_position = self.get_perp_position_with_lp_settle(market_index)[ + 0 + ] or self.get_empty_position(market_index) + + market = self.drift_client.get_perp_market_account(perp_position.market_index) + + perp_position_value = calculate_base_asset_value_with_oracle( + market, perp_position, oracle_price_data, include_open_orders + ) + + return perp_position_value diff --git a/src/driftpy/idl/drift.json b/src/driftpy/idl/drift.json index 6839e90c..b9c1b64b 100644 --- a/src/driftpy/idl/drift.json +++ b/src/driftpy/idl/drift.json @@ -1,5 +1,5 @@ { - "version": "2.85.0", + "version": "2.86.0", "name": "drift", "instructions": [ { @@ -12762,8 +12762,5 @@ "name": "InvalidOpenbookV2Market", "msg": "InvalidOpenbookV2Market" } - ], - "metadata": { - "address": "dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH" - } + ] } \ No newline at end of file diff --git a/src/driftpy/math/fuel.py b/src/driftpy/math/fuel.py new file mode 100644 index 00000000..48304d86 --- /dev/null +++ b/src/driftpy/math/fuel.py @@ -0,0 +1,56 @@ +from driftpy.types import SpotMarketAccount, PerpMarketAccount +from driftpy.constants.numeric_constants import QUOTE_PRECISION, FUEL_WINDOW + + +def calculate_insurance_fuel_bonus( + spot_market: SpotMarketAccount, token_stake_amount: int, fuel_bonus_numerator: int +) -> int: + insurance_fund_fuel = ( + abs(token_stake_amount) * fuel_bonus_numerator + ) * spot_market.fuel_boost_insurance + insurace_fund_fuel_per_day = insurance_fund_fuel // FUEL_WINDOW + insurance_fund_fuel_scaled = insurace_fund_fuel_per_day // (QUOTE_PRECISION // 10) + + return insurance_fund_fuel_scaled + + +def calculate_spot_fuel_bonus( + spot_market: SpotMarketAccount, signed_token_value: int, fuel_bonus_numerator: int +) -> int: + spot_fuel_scaled: int + + # dust + if abs(signed_token_value) <= QUOTE_PRECISION: + spot_fuel_scaled = 0 + elif signed_token_value > 0: + deposit_fuel = ( + abs(signed_token_value) * fuel_bonus_numerator + ) * spot_market.fuel_boost_deposits + deposit_fuel_per_day = deposit_fuel // FUEL_WINDOW + spot_fuel_scaled = deposit_fuel_per_day // (QUOTE_PRECISION // 10) + else: + borrow_fuel = ( + abs(signed_token_value) * fuel_bonus_numerator + ) * spot_market.fuel_boost_borrows + borrow_fuel_per_day = borrow_fuel // FUEL_WINDOW + spot_fuel_scaled = borrow_fuel_per_day // (QUOTE_PRECISION // 10) + + return spot_fuel_scaled + + +def calculate_perp_fuel_bonus( + perp_market: PerpMarketAccount, base_asset_value: int, fuel_bonus_numerator: int +) -> int: + perp_fuel_scaled: int + + # dust + if abs(base_asset_value) <= QUOTE_PRECISION: + perp_fuel_scaled = 0 + else: + perp_fuel = ( + abs(base_asset_value) * fuel_bonus_numerator + ) * perp_market.fuel_boost_position + perp_fuel_per_day = perp_fuel // FUEL_WINDOW + perp_fuel_scaled = perp_fuel_per_day // (QUOTE_PRECISION // 10) + + return perp_fuel_scaled diff --git a/src/driftpy/types.py b/src/driftpy/types.py index 0a2f231a..6abc5880 100644 --- a/src/driftpy/types.py +++ b/src/driftpy/types.py @@ -9,7 +9,9 @@ from typing import Optional from urllib.parse import urlparse, urlunparse -from solders.pubkey import Pubkey # type: ignore +from solders.pubkey import Pubkey + +from driftpy.constants.numeric_constants import FUEL_START_TS # type: ignore def is_variant(enum, type: str) -> bool: @@ -700,7 +702,10 @@ class PerpMarketAccount: padding1: int = 0 quote_spot_market_index: Optional[int] = None fee_adjustment: Optional[int] = None - padding: list[int] = field(default_factory=lambda: [0] * 46) + fuel_boost_taker: Optional[int] = None + fuel_boost_maker: Optional[int] = None + fuel_boost_position: Optional[int] = None + padding: list[int] = field(default_factory=lambda: [0] * 43) @dataclass @@ -774,12 +779,21 @@ class SpotMarketAccount: oracle_source: OracleSource status: MarketStatus asset_tier: AssetTier - padding1: list[int] = field(default_factory=lambda: [0] * 6) + paused_operations: int + if_paused_operations: int + fee_adjustment: int + max_token_borrows_fraction: int flash_loan_amount: Optional[int] = None flash_loan_initial_token_amount: Optional[int] = None total_swap_fee: Optional[int] = None scale_initial_asset_weight_start: Optional[int] = None - padding: list[int] = field(default_factory=lambda: [0] * 48) + min_borrow_rate: Optional[int] = None + fuel_boost_deposits: Optional[int] = None + fuel_boost_borrows: Optional[int] = None + fuel_boost_taker: Optional[int] = None + fuel_boost_maker: Optional[int] = None + fuel_boost_insurance: Optional[int] = None + padding: list[int] = field(default_factory=lambda: [0] * 42) @dataclass @@ -857,6 +871,7 @@ class UserAccount: has_open_order: bool open_auctions: int has_open_auction: bool + last_fuel_bonus_update_ts: int padding: list[int] = field(default_factory=lambda: [0] * 21) @@ -893,7 +908,16 @@ class UserStatsAccount: number_of_sub_accounts_created: int is_referrer: bool disable_update_perp_bid_ask_twap: bool - padding: list[int] = field(default_factory=lambda: [0] * 50) + padding1: list[int] = (field(default_factory=lambda: [0] * 2),) + last_fuel_bonus_update_ts: int = (0,) + fuel_insurance: int = (0,) + fuel_deposits: int = (0,) + fuel_borrows: int = (0,) + fuel_positions: int = (0,) + fuel_taker: int = (0,) + fuel_maker: int = (0,) + if_staked_gov_token_amount: int = (0,) + padding: list[int] = field(default_factory=lambda: [0] * 12) @dataclass diff --git a/tests/dlob_test_constants.py b/tests/dlob_test_constants.py index cb8b1e5c..30d92ddd 100644 --- a/tests/dlob_test_constants.py +++ b/tests/dlob_test_constants.py @@ -305,9 +305,11 @@ oracle_source=OracleSource.Pyth(), historical_oracle_data=mock_historical_oracle_data, historical_index_data=mock_historical_index_data, - padding1=[0] * 6, - padding=[0] * 48, expiry_ts=0, + paused_operations=0, + if_paused_operations=0, + fee_adjustment=0, + max_token_borrows_fraction=0, ), SpotMarketAccount( status=MarketStatus.Active(), @@ -360,9 +362,11 @@ oracle_source=OracleSource.Pyth(), historical_oracle_data=mock_historical_oracle_data, historical_index_data=mock_historical_index_data, - padding1=[0] * 6, - padding=[0] * 48, expiry_ts=0, + paused_operations=0, + if_paused_operations=0, + fee_adjustment=0, + max_token_borrows_fraction=0, ), SpotMarketAccount( status=MarketStatus.Active(), @@ -415,8 +419,10 @@ oracle_source=OracleSource.Pyth(), historical_oracle_data=mock_historical_oracle_data, historical_index_data=mock_historical_index_data, - padding1=[0] * 6, - padding=[0] * 48, expiry_ts=0, + paused_operations=0, + if_paused_operations=0, + fee_adjustment=0, + max_token_borrows_fraction=0, ), ] diff --git a/tests/math/helpers.py b/tests/math/helpers.py index 2675a002..104bfc5c 100644 --- a/tests/math/helpers.py +++ b/tests/math/helpers.py @@ -108,6 +108,7 @@ has_open_order=False, open_auctions=0, has_open_auction=False, + last_fuel_bonus_update_ts=0, padding=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ) diff --git a/update_idl.sh b/update_idl.sh index 6be6b055..c035c2d1 100644 --- a/update_idl.sh +++ b/update_idl.sh @@ -1,3 +1,4 @@ +git submodule update --remote --merge --recursive && cd protocol-v2/ && anchor build && cp target/idl/* ../src/driftpy/idl/ \ No newline at end of file