hylo_core/
fee_controller.rs

1use anchor_lang::prelude::*;
2use fix::prelude::*;
3
4use crate::error::CoreError::{
5  FeeExtraction, InvalidFees, NoValidLevercoinMintFee,
6  NoValidLevercoinRedeemFee, NoValidStablecoinMintFee, NoValidSwapFee,
7};
8use crate::stability_mode::StabilityMode::{self, Depeg, Mode1, Mode2, Normal};
9
10/// Represents the spread of fees between mint and redeem for protocol tokens.
11/// All fees must be in basis points to represent a fractional percentage
12/// directly applicable to a token amount e.g. `0.XXXX` or `bips x 10^-4`.
13#[derive(Copy, Clone, InitSpace, AnchorSerialize, AnchorDeserialize)]
14pub struct FeePair {
15  mint: UFixValue64,
16  redeem: UFixValue64,
17}
18
19impl FeePair {
20  #[must_use]
21  pub fn new(mint: UFixValue64, redeem: UFixValue64) -> FeePair {
22    FeePair { mint, redeem }
23  }
24
25  pub fn mint(&self) -> Result<UFix64<N4>> {
26    self.mint.try_into()
27  }
28
29  pub fn redeem(&self) -> Result<UFix64<N4>> {
30    self.redeem.try_into()
31  }
32
33  /// Fees must be less than 100%
34  pub fn validate(&self) -> Result<()> {
35    let one = UFix64::one();
36    if self.mint()? < one && self.redeem()? < one {
37      Ok(())
38    } else {
39      Err(InvalidFees.into())
40    }
41  }
42}
43
44/// Fee configuration table reacts to different stability modes.
45pub trait FeeController {
46  fn mint_fee(&self, mode: StabilityMode) -> Result<UFix64<N4>>;
47  fn redeem_fee(&self, mode: StabilityMode) -> Result<UFix64<N4>>;
48  fn validate(&self) -> Result<()>;
49}
50
51/// Combines fee multiplication for a token amount with the remaining token
52/// amount by subtraction.
53pub struct FeeExtract<Exp> {
54  pub fees_extracted: UFix64<Exp>,
55  pub amount_remaining: UFix64<Exp>,
56}
57
58impl<Exp> FeeExtract<Exp> {
59  pub fn new(
60    fee: UFix64<N4>,
61    amount_in: UFix64<Exp>,
62  ) -> Result<FeeExtract<Exp>> {
63    let fees_extracted = amount_in
64      .mul_div_ceil(fee, UFix64::<N4>::one())
65      .ok_or(FeeExtraction)?;
66
67    let amount_remaining = amount_in
68      .checked_sub(&fees_extracted)
69      .ok_or(FeeExtraction)?;
70
71    Ok(FeeExtract {
72      fees_extracted,
73      amount_remaining,
74    })
75  }
76}
77
78#[derive(Copy, Clone, InitSpace, AnchorSerialize, AnchorDeserialize)]
79pub struct StablecoinFees {
80  normal: FeePair,
81  mode_1: FeePair,
82}
83
84impl StablecoinFees {
85  #[must_use]
86  pub fn new(normal: FeePair, mode_1: FeePair) -> StablecoinFees {
87    StablecoinFees { normal, mode_1 }
88  }
89}
90
91impl FeeController for StablecoinFees {
92  /// Determines fee to charge when minting `hyUSD`
93  /// Fee increases in mode 1, and minting fails in mode 2.
94  fn mint_fee(&self, mode: StabilityMode) -> Result<UFix64<N4>> {
95    match mode {
96      Normal => self.normal.mint.try_into(),
97      Mode1 => self.mode_1.mint.try_into(),
98      Mode2 | Depeg => Err(NoValidStablecoinMintFee.into()),
99    }
100  }
101
102  /// Determines fee to charge when redeeming `hyUSD`.
103  fn redeem_fee(&self, mode: StabilityMode) -> Result<UFix64<N4>> {
104    match mode {
105      Normal => self.normal.redeem.try_into(),
106      Mode1 => self.mode_1.redeem.try_into(),
107      Mode2 | Depeg => Ok(UFix64::zero()),
108    }
109  }
110
111  /// Run validations
112  fn validate(&self) -> Result<()> {
113    self.normal.validate()?;
114    self.mode_1.validate()
115  }
116}
117
118#[derive(Copy, Clone, InitSpace, AnchorDeserialize, AnchorSerialize)]
119pub struct LevercoinFees {
120  normal: FeePair,
121  mode_1: FeePair,
122  mode_2: FeePair,
123}
124
125impl FeeController for LevercoinFees {
126  /// Determines fee to charge when minting `xSOL`.
127  /// Fees should become cheaper or zero as protocol goes into stability modes.
128  fn mint_fee(&self, mode: StabilityMode) -> Result<UFix64<N4>> {
129    match mode {
130      Normal => self.normal.mint.try_into(),
131      Mode1 => self.mode_1.mint.try_into(),
132      Mode2 => self.mode_2.mint.try_into(),
133      Depeg => Err(NoValidLevercoinMintFee.into()),
134    }
135  }
136
137  /// Determines fee to charge when redeeming `xSOL`.
138  /// Fees get increasingly more expensive in stability modes.
139  fn redeem_fee(&self, mode: StabilityMode) -> Result<UFix64<N4>> {
140    match mode {
141      Normal => self.normal.redeem.try_into(),
142      Mode1 => self.mode_1.redeem.try_into(),
143      Mode2 => self.mode_2.redeem.try_into(),
144      Depeg => Err(NoValidLevercoinRedeemFee.into()),
145    }
146  }
147
148  /// Run validations
149  fn validate(&self) -> Result<()> {
150    self.normal.validate()?;
151    self.mode_1.validate()?;
152    self.mode_2.validate()
153  }
154}
155
156impl LevercoinFees {
157  #[must_use]
158  pub fn new(
159    normal: FeePair,
160    mode_1: FeePair,
161    mode_2: FeePair,
162  ) -> LevercoinFees {
163    LevercoinFees {
164      normal,
165      mode_1,
166      mode_2,
167    }
168  }
169
170  /// Fees to charge in the levercoin to stablecoin swap.
171  pub fn swap_to_stablecoin_fee(
172    &self,
173    mode: StabilityMode,
174  ) -> Result<UFix64<N4>> {
175    match mode {
176      Normal => self.normal.redeem.try_into(),
177      Mode1 => self.mode_1.redeem.try_into(),
178      Mode2 | Depeg => Err(NoValidSwapFee.into()),
179    }
180  }
181
182  /// Fees to charge in the stablecoin to levercoin swap.
183  pub fn swap_from_stablecoin_fee(
184    &self,
185    mode: StabilityMode,
186  ) -> Result<UFix64<N4>> {
187    match mode {
188      Normal => self.normal.mint(),
189      Mode1 => self.mode_1.mint(),
190      Mode2 => self.mode_2.mint(),
191      Depeg => Err(NoValidSwapFee.into()),
192    }
193  }
194}
195
196#[cfg(test)]
197mod tests {
198  use super::*;
199
200  #[test]
201  fn fee_extraction() -> Result<()> {
202    let fee = UFix64::new(50);
203    let amount = UFix64::<N9>::new(69_618_816_010);
204    let out = FeeExtract::new(fee, amount)?;
205    assert_eq!(out.fees_extracted, UFix64::new(348_094_081));
206    assert_eq!(out.amount_remaining, UFix64::new(69_270_721_929));
207    Ok(())
208  }
209
210  #[test]
211  fn fee_extraction_underflow() {
212    let fee = UFix64::new(10001);
213    let amount = UFix64::<N9>::new(69_618_816_010);
214    let out = FeeExtract::new(fee, amount);
215    assert!(out.is_err());
216  }
217}