gmsol_model/action/
update_funding_state.rs

1use num_traits::{CheckedDiv, Zero};
2
3use crate::{
4    fixed::FixedPointOps,
5    market::{BaseMarket, BaseMarketExt, PerpMarketMutExt},
6    num::{MulDiv, Unsigned},
7    params::fee::FundingRateChangeType,
8    price::Prices,
9    Balance, BalanceExt, PerpMarketMut,
10};
11
12use super::MarketAction;
13
14/// Update Funding State Action.
15#[must_use = "actions do nothing unless you `execute` them"]
16pub struct UpdateFundingState<M: BaseMarket<DECIMALS>, const DECIMALS: u8> {
17    market: M,
18    prices: Prices<M::Num>,
19}
20
21impl<M: PerpMarketMut<DECIMALS>, const DECIMALS: u8> UpdateFundingState<M, DECIMALS> {
22    /// Create a new [`UpdateFundingState`] action.
23    pub fn try_new(market: M, prices: &Prices<M::Num>) -> crate::Result<Self> {
24        prices.validate()?;
25        Ok(Self {
26            market,
27            prices: prices.clone(),
28        })
29    }
30
31    /// Calculate next funding amounts per size.
32    pub fn next_funding_amount_per_size(
33        &self,
34        duration_in_seconds: u64,
35    ) -> crate::Result<UpdateFundingReport<M::Num, <M::Num as Unsigned>::Signed>> {
36        use crate::utils;
37        use num_traits::{CheckedMul, FromPrimitive};
38
39        let mut report = UpdateFundingReport::empty(duration_in_seconds);
40        let open_interest = self.market.open_interest()?;
41        let long_open_interest = open_interest.long_amount()?;
42        let short_open_interest = open_interest.short_amount()?;
43
44        if long_open_interest.is_zero() || short_open_interest.is_zero() {
45            return Ok(report);
46        }
47
48        let (funding_factor_per_second, longs_pay_shorts, next_funding_factor_per_second) = self
49            .next_funding_factor_per_second(
50                duration_in_seconds,
51                &long_open_interest,
52                &short_open_interest,
53            )?;
54        report.next_funding_factor_per_second = next_funding_factor_per_second;
55
56        let size_of_larger_side = if long_open_interest > short_open_interest {
57            long_open_interest.clone()
58        } else {
59            short_open_interest.clone()
60        };
61        let duration_value = M::Num::from_u64(duration_in_seconds).ok_or(crate::Error::Convert)?;
62        let funding_factor = duration_value
63            .checked_mul(&funding_factor_per_second)
64            .ok_or(crate::Error::Computation("calculating funding factor"))?;
65        let funding_value = utils::apply_factor(&size_of_larger_side, &funding_factor)
66            .ok_or(crate::Error::Computation("calculating funding value"))?;
67
68        let payer_open_interest = if longs_pay_shorts {
69            &long_open_interest
70        } else {
71            &short_open_interest
72        };
73        let for_long_collateral = funding_value
74            .checked_mul_div(
75                &self
76                    .market
77                    .open_interest_pool(longs_pay_shorts)?
78                    .long_amount()?,
79                payer_open_interest,
80            )
81            .ok_or(crate::Error::Computation(
82                "calculating funding value for long collateral",
83            ))?;
84        let for_short_collateral = funding_value
85            .checked_mul_div(
86                &self
87                    .market
88                    .open_interest_pool(longs_pay_shorts)?
89                    .short_amount()?,
90                payer_open_interest,
91            )
92            .ok_or(crate::Error::Computation(
93                "calculating funding value for short collateral",
94            ))?;
95
96        self.set_deltas(
97            &mut report,
98            longs_pay_shorts,
99            &for_long_collateral,
100            &for_short_collateral,
101            if !longs_pay_shorts {
102                &long_open_interest
103            } else {
104                &short_open_interest
105            },
106        )?;
107
108        Ok(report)
109    }
110
111    fn set_deltas(
112        &self,
113        report: &mut UpdateFundingReport<M::Num, <M::Num as Unsigned>::Signed>,
114        longs_pay_shorts: bool,
115        for_long_collateral: &M::Num,
116        for_short_collateral: &M::Num,
117        receiver_interest: &M::Num,
118    ) -> crate::Result<()> {
119        let adjustment = &self.market.funding_amount_per_size_adjustment();
120        for is_long_collateral in [true, false] {
121            let (funding_value, price) = if is_long_collateral {
122                (
123                    for_long_collateral,
124                    self.prices.long_token_price.pick_price(true),
125                )
126            } else {
127                (
128                    for_short_collateral,
129                    self.prices.short_token_price.pick_price(true),
130                )
131            };
132
133            let payer = flags_to_index(longs_pay_shorts, is_long_collateral);
134            let receiver = flags_to_index(!longs_pay_shorts, is_long_collateral);
135
136            report.delta_funding_amount_per_size[payer] = pack_to_funding_amount_per_size(
137                adjustment,
138                funding_value,
139                &self
140                    .market
141                    .open_interest_pool(longs_pay_shorts)?
142                    .amount(is_long_collateral)?,
143                price,
144                true,
145            )
146            .ok_or(crate::Error::Computation(
147                "calculating delta funding amount per size",
148            ))?;
149
150            report.delta_claimable_funding_amount_per_size[receiver] =
151                pack_to_funding_amount_per_size(
152                    adjustment,
153                    funding_value,
154                    receiver_interest,
155                    price,
156                    false,
157                )
158                .ok_or(crate::Error::Computation(
159                    "calculating delta claimable funding amount per size",
160                ))?;
161        }
162        Ok(())
163    }
164
165    /// Get next funding factor per second.
166    pub fn next_funding_factor_per_second(
167        &self,
168        duration_in_seconds: u64,
169        long_open_interest: &M::Num,
170        short_open_interest: &M::Num,
171    ) -> crate::Result<(M::Num, bool, M::Signed)> {
172        use crate::{num::UnsignedAbs, utils};
173        use num_traits::{CheckedAdd, CheckedMul, CheckedSub, FromPrimitive, Signed};
174
175        let params = self.market.funding_fee_params()?;
176        let funding_increase_factor_per_second = params.increase_factor_per_second();
177
178        let diff_value = long_open_interest.clone().diff(short_open_interest.clone());
179
180        if diff_value.is_zero() && funding_increase_factor_per_second.is_zero() {
181            return Ok((Zero::zero(), true, Zero::zero()));
182        }
183
184        let total_open_interest = long_open_interest
185            .checked_add(short_open_interest)
186            .ok_or(crate::Error::Computation("calculating total open interest"))?;
187
188        if total_open_interest.is_zero() {
189            return Err(crate::Error::UnableToGetFundingFactorEmptyOpenInterest);
190        }
191
192        let diff_value_after_exponent =
193            utils::apply_exponent_factor(diff_value, params.exponent().clone()).ok_or(
194                crate::Error::Computation("applying exponent factor to diff value"),
195            )?;
196        let diff_value_to_open_interest_factor =
197            utils::div_to_factor(&diff_value_after_exponent, &total_open_interest, false).ok_or(
198                crate::Error::Computation("calculating diff value to open interest factor"),
199            )?;
200
201        if funding_increase_factor_per_second.is_zero() {
202            let mut funding_factor_per_second =
203                utils::apply_factor(&diff_value_to_open_interest_factor, params.factor()).ok_or(
204                    crate::Error::Computation("calculating fallback funding factor per second"),
205                )?;
206
207            if funding_factor_per_second > *params.max_factor_per_second() {
208                funding_factor_per_second = params.max_factor_per_second().clone();
209            }
210
211            return Ok((
212                funding_factor_per_second,
213                long_open_interest > short_open_interest,
214                Zero::zero(),
215            ));
216        }
217
218        let funding_factor_per_second = self.market.funding_factor_per_second();
219        let funding_factor_per_second_magnitude = funding_factor_per_second.unsigned_abs();
220
221        let change = params.change(
222            funding_factor_per_second,
223            long_open_interest,
224            short_open_interest,
225            &diff_value_to_open_interest_factor,
226        );
227
228        let duration_value = M::Num::from_u64(duration_in_seconds).ok_or(crate::Error::Convert)?;
229        let next_funding_factor_per_second = match change {
230            FundingRateChangeType::Increase => {
231                let increase_value = utils::apply_factor(
232                    &diff_value_to_open_interest_factor,
233                    funding_increase_factor_per_second,
234                )
235                .and_then(|v| v.checked_mul(&duration_value))
236                .ok_or(crate::Error::Computation(
237                    "calculating factor increase value",
238                ))?;
239
240                let increase_value = if long_open_interest < short_open_interest {
241                    increase_value.to_opposite_signed()?
242                } else {
243                    increase_value.to_signed()?
244                };
245
246                funding_factor_per_second
247                    .checked_add(&increase_value)
248                    .ok_or(crate::Error::Computation("increasing funding factor"))?
249            }
250            FundingRateChangeType::Decrease if !funding_factor_per_second_magnitude.is_zero() => {
251                let decrease_value = params
252                    .decrease_factor_per_second()
253                    .checked_mul(&duration_value)
254                    .ok_or(crate::Error::Computation(
255                        "calculating factor decrease value",
256                    ))?;
257                if funding_factor_per_second_magnitude <= decrease_value {
258                    funding_factor_per_second
259                        .checked_div(&funding_factor_per_second_magnitude.to_signed()?)
260                        .ok_or(crate::Error::Computation("calculating signum"))?
261                } else {
262                    let decreased = funding_factor_per_second_magnitude
263                        .checked_sub(&decrease_value)
264                        .ok_or(crate::Error::Computation(
265                            "calculating decreased funding factor per second (infallible)",
266                        ))?;
267                    if funding_factor_per_second.is_negative() {
268                        decreased.to_opposite_signed()?
269                    } else {
270                        decreased.to_signed()?
271                    }
272                }
273            }
274            _ => funding_factor_per_second.clone(),
275        };
276
277        let next_funding_factor_per_second = Unsigned::bound_magnitude(
278            &next_funding_factor_per_second,
279            &Zero::zero(),
280            params.max_factor_per_second(),
281        )?;
282
283        let next_funding_factor_per_second_with_min_bound = Unsigned::bound_magnitude(
284            &next_funding_factor_per_second,
285            params.min_factor_per_second(),
286            params.max_factor_per_second(),
287        )?;
288
289        Ok((
290            next_funding_factor_per_second_with_min_bound.unsigned_abs(),
291            next_funding_factor_per_second_with_min_bound.is_positive(),
292            next_funding_factor_per_second,
293        ))
294    }
295}
296
297impl<M: PerpMarketMut<DECIMALS>, const DECIMALS: u8> MarketAction
298    for UpdateFundingState<M, DECIMALS>
299{
300    type Report = UpdateFundingReport<M::Num, <M::Num as Unsigned>::Signed>;
301
302    fn execute(mut self) -> crate::Result<Self::Report> {
303        const MATRIX: [(bool, bool); 4] =
304            [(true, true), (true, false), (false, true), (false, false)];
305        let duration_in_seconds = self.market.just_passed_in_seconds_for_funding()?;
306        let report = self.next_funding_amount_per_size(duration_in_seconds)?;
307        for (is_long, is_long_collateral) in MATRIX {
308            self.market.apply_delta_to_funding_amount_per_size(
309                is_long,
310                is_long_collateral,
311                &report
312                    .delta_funding_amount_per_size(is_long, is_long_collateral)
313                    .to_signed()?,
314            )?;
315            self.market
316                .apply_delta_to_claimable_funding_amount_per_size(
317                    is_long,
318                    is_long_collateral,
319                    &report
320                        .delta_claimable_funding_amount_per_size(is_long, is_long_collateral)
321                        .to_signed()?,
322                )?;
323        }
324        *self.market.funding_factor_per_second_mut() =
325            report.next_funding_factor_per_second().clone();
326        Ok(report)
327    }
328}
329
330/// Update Funding Report.
331#[derive(Debug)]
332#[cfg_attr(
333    feature = "anchor-lang",
334    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
335)]
336pub struct UpdateFundingReport<Unsigned, Signed> {
337    duration_in_seconds: u64,
338    next_funding_factor_per_second: Signed,
339    delta_funding_amount_per_size: [Unsigned; 4],
340    delta_claimable_funding_amount_per_size: [Unsigned; 4],
341}
342
343#[cfg(feature = "gmsol-utils")]
344impl<Unsigned, Signed> gmsol_utils::InitSpace for UpdateFundingReport<Unsigned, Signed>
345where
346    Unsigned: gmsol_utils::InitSpace,
347    Signed: gmsol_utils::InitSpace,
348{
349    const INIT_SPACE: usize =
350        u64::INIT_SPACE + Signed::INIT_SPACE + 4 * Unsigned::INIT_SPACE + 4 * Unsigned::INIT_SPACE;
351}
352
353#[inline]
354fn flags_to_index(is_long: bool, is_long_collateral: bool) -> usize {
355    match (is_long_collateral, is_long) {
356        (true, true) => 0,
357        (true, false) => 1,
358        (false, true) => 2,
359        (false, false) => 3,
360    }
361}
362
363impl<T: Unsigned> UpdateFundingReport<T, T::Signed> {
364    /// Create a new empty report.
365    pub fn empty(duration_in_seconds: u64) -> Self {
366        Self {
367            duration_in_seconds,
368            next_funding_factor_per_second: Zero::zero(),
369            delta_funding_amount_per_size: [Zero::zero(), Zero::zero(), Zero::zero(), Zero::zero()],
370            delta_claimable_funding_amount_per_size: [
371                Zero::zero(),
372                Zero::zero(),
373                Zero::zero(),
374                Zero::zero(),
375            ],
376        }
377    }
378
379    /// Get considered duration in seconds.
380    pub fn duration_in_seconds(&self) -> u64 {
381        self.duration_in_seconds
382    }
383
384    /// Get next funding factor per second.
385    #[inline]
386    pub fn next_funding_factor_per_second(&self) -> &T::Signed {
387        &self.next_funding_factor_per_second
388    }
389
390    /// Get delta to funding amount per size.
391    #[inline]
392    pub fn delta_funding_amount_per_size(&self, is_long: bool, is_long_collateral: bool) -> &T {
393        let idx = flags_to_index(is_long, is_long_collateral);
394        &self.delta_funding_amount_per_size[idx]
395    }
396
397    /// Get delta to claimable funding amount per size.
398    #[inline]
399    pub fn delta_claimable_funding_amount_per_size(
400        &self,
401        is_long: bool,
402        is_long_collateral: bool,
403    ) -> &T {
404        let idx = flags_to_index(is_long, is_long_collateral);
405        &self.delta_claimable_funding_amount_per_size[idx]
406    }
407}
408
409/// Pack the value to funding amount per size with the given `adjustment`.
410pub fn pack_to_funding_amount_per_size<T, const DECIMALS: u8>(
411    adjustment: &T,
412    funding_value: &T,
413    open_interest: &T,
414    price: &T,
415    round_up_magnitude: bool,
416) -> Option<T>
417where
418    T: FixedPointOps<DECIMALS>,
419{
420    if funding_value.is_zero() || open_interest.is_zero() {
421        return Some(Zero::zero());
422    }
423
424    let numerator = adjustment.checked_mul(&T::UNIT)?;
425    let funding_value_per_size = if round_up_magnitude {
426        funding_value.checked_mul_div_ceil(&numerator, open_interest)?
427    } else {
428        funding_value.checked_mul_div(&numerator, open_interest)?
429    };
430
431    debug_assert!(!price.is_zero(), "must be non-zero");
432    if round_up_magnitude {
433        funding_value_per_size.checked_round_up_div(price)
434    } else {
435        funding_value_per_size.checked_div(price)
436    }
437}
438
439/// Calculate the funding amount for a position and unpack with the given `adjustment`.
440pub fn unpack_to_funding_amount_delta<T, const DECIMALS: u8>(
441    adjustment: &T,
442    latest_funding_amount_per_size: &T,
443    position_funding_amount_per_size: &T,
444    size_in_usd: &T,
445    round_up_magnitude: bool,
446) -> Option<T>
447where
448    T: FixedPointOps<DECIMALS>,
449{
450    let funding_diff_factor =
451        latest_funding_amount_per_size.checked_sub(position_funding_amount_per_size)?;
452
453    let adjustment = adjustment.checked_mul(&T::UNIT)?;
454    if round_up_magnitude {
455        size_in_usd.checked_mul_div_ceil(&funding_diff_factor, &adjustment)
456    } else {
457        size_in_usd.checked_mul_div(&funding_diff_factor, &adjustment)
458    }
459}
460
461#[cfg(test)]
462mod tests {
463    use std::{thread::sleep, time::Duration};
464
465    use crate::{
466        market::LiquidityMarketMutExt,
467        test::{TestMarket, TestPosition},
468        MarketAction, PositionMutExt,
469    };
470
471    use super::*;
472
473    #[test]
474    fn test_update_funding_state() -> crate::Result<()> {
475        let mut market = TestMarket::<u64, 9>::default();
476        let prices = Prices::new_for_test(120, 120, 1);
477        market
478            .deposit(1_000_000_000_000, 100_000_000_000_000, prices)?
479            .execute()?;
480        println!("{market:#?}");
481        let mut long = TestPosition::long(true);
482        let mut short = TestPosition::short(false);
483        let prices = Prices::new_for_test(123, 123, 1);
484        let report = long
485            .ops(&mut market)
486            .increase(prices, 1_000_000_000_000, 50_000_000_000_000, None)?
487            .execute()?;
488        println!("{report:#?}");
489        let report = short
490            .ops(&mut market)
491            .increase(prices, 100_000_000_000_000, 25_000_000_000_000, None)?
492            .execute()?;
493        println!("{report:#?}");
494        println!("{market:#?}");
495        sleep(Duration::from_secs(2));
496        let report = long
497            .ops(&mut market)
498            .decrease(prices, 50_000_000_000_000, None, 0, Default::default())?
499            .execute()?;
500        println!("{report:#?}");
501        let report = short
502            .ops(&mut market)
503            .decrease(prices, 25_000_000_000_000, None, 0, Default::default())?
504            .execute()?;
505        println!("{report:#?}");
506        println!("{market:#?}");
507        Ok(())
508    }
509}