Skip to main content

mars_params/types/
asset.rs

1use cosmwasm_schema::cw_serde;
2use cosmwasm_std::{Addr, Api, Decimal, Uint128};
3use mars_utils::{
4    error::ValidationError,
5    helpers::{decimal_param_le_one, decimal_param_lt_one, validate_native_denom},
6};
7
8use crate::{
9    error::ContractResult,
10    execute::{assert_hls_lqt_gt_max_ltv, assert_lqt_gt_max_ltv},
11    types::hls::HlsParamsBase,
12};
13
14#[cw_serde]
15pub struct CmSettings<T> {
16    pub whitelisted: bool,
17    pub hls: Option<HlsParamsBase<T>>,
18}
19
20#[cw_serde]
21pub struct RedBankSettings {
22    pub deposit_enabled: bool,
23    pub borrow_enabled: bool,
24    pub deposit_cap: Uint128,
25}
26
27/// The LB will depend on the Health Factor and a couple other parameters as follows:
28/// Liquidation Bonus = min(
29///     b + (slope * (1 - HF)),
30///     max(
31///         min(CR - 1, max_lb),
32///         min_lb
33///     )
34/// )
35#[cw_serde]
36pub struct LiquidationBonus {
37    /// Marks the level at which the LB starts when HF drops marginally below 1.
38    /// If set at 1%, at HF = 0.999 the LB will be 1%. If set at 0%, the LB starts increasing from 0% as the HF drops below 1.
39    pub starting_lb: Decimal,
40    /// Defines the slope at which the LB increases as the HF decreases.
41    /// The higher the slope, the faster the LB increases as the HF decreases.
42    pub slope: Decimal,
43    /// Minimum LB that will be granted to liquidators even when the position is undercollateralized.
44    pub min_lb: Decimal,
45    /// Maximum LB that can be granted to a liquidator; in other words, the maxLB establishes a ceiling to the LB.
46    /// This is a precautionary parameter to mitigate liquidated users being over-punished.
47    pub max_lb: Decimal,
48}
49
50impl LiquidationBonus {
51    pub fn validate(&self) -> Result<(), ValidationError> {
52        assert_starting_lb_within_range(self.starting_lb)?;
53        assert_lb_slope_within_range(self.slope)?;
54        assert_min_lb_within_range(self.min_lb)?;
55        assert_max_lb_within_range(self.max_lb)?;
56        assert_max_lb_gt_min_lb(self.min_lb, self.max_lb)?;
57        Ok(())
58    }
59}
60
61fn assert_starting_lb_within_range(b: Decimal) -> Result<(), ValidationError> {
62    if b > Decimal::percent(10) {
63        return Err(ValidationError::InvalidParam {
64            param_name: "starting_lb".to_string(),
65            invalid_value: b.to_string(),
66            predicate: "[0, 0.1]".to_string(),
67        });
68    }
69    Ok(())
70}
71
72fn assert_lb_slope_within_range(slope: Decimal) -> Result<(), ValidationError> {
73    if slope < Decimal::one() || slope > Decimal::from_ratio(5u8, 1u8) {
74        return Err(ValidationError::InvalidParam {
75            param_name: "slope".to_string(),
76            invalid_value: slope.to_string(),
77            predicate: "[1, 5]".to_string(),
78        });
79    }
80    Ok(())
81}
82
83fn assert_min_lb_within_range(min_lb: Decimal) -> Result<(), ValidationError> {
84    if min_lb > Decimal::percent(10) {
85        return Err(ValidationError::InvalidParam {
86            param_name: "min_lb".to_string(),
87            invalid_value: min_lb.to_string(),
88            predicate: "[0, 0.1]".to_string(),
89        });
90    }
91    Ok(())
92}
93
94fn assert_max_lb_within_range(max_lb: Decimal) -> Result<(), ValidationError> {
95    if max_lb < Decimal::percent(5) || max_lb > Decimal::percent(30) {
96        return Err(ValidationError::InvalidParam {
97            param_name: "max_lb".to_string(),
98            invalid_value: max_lb.to_string(),
99            predicate: "[0.05, 0.3]".to_string(),
100        });
101    }
102    Ok(())
103}
104
105fn assert_max_lb_gt_min_lb(min_lb: Decimal, max_lb: Decimal) -> Result<(), ValidationError> {
106    if min_lb > max_lb {
107        return Err(ValidationError::InvalidParam {
108            param_name: "max_lb".to_string(),
109            invalid_value: max_lb.to_string(),
110            predicate: format!("> {} (min LB)", min_lb),
111        });
112    }
113    Ok(())
114}
115
116#[cw_serde]
117pub struct AssetParamsBase<T> {
118    pub denom: String,
119    pub credit_manager: CmSettings<T>,
120    pub red_bank: RedBankSettings,
121    pub max_loan_to_value: Decimal,
122    pub liquidation_threshold: Decimal,
123    pub liquidation_bonus: LiquidationBonus,
124    pub protocol_liquidation_fee: Decimal,
125}
126
127pub type AssetParams = AssetParamsBase<Addr>;
128pub type AssetParamsUnchecked = AssetParamsBase<String>;
129
130impl From<AssetParams> for AssetParamsUnchecked {
131    fn from(p: AssetParams) -> Self {
132        Self {
133            denom: p.denom,
134            credit_manager: CmSettings {
135                whitelisted: p.credit_manager.whitelisted,
136                hls: p.credit_manager.hls.map(Into::into),
137            },
138            red_bank: p.red_bank,
139            max_loan_to_value: p.max_loan_to_value,
140            liquidation_threshold: p.liquidation_threshold,
141            liquidation_bonus: p.liquidation_bonus,
142            protocol_liquidation_fee: p.protocol_liquidation_fee,
143        }
144    }
145}
146
147impl AssetParamsUnchecked {
148    pub fn check(&self, api: &dyn Api) -> ContractResult<AssetParams> {
149        validate_native_denom(&self.denom)?;
150
151        decimal_param_lt_one(self.max_loan_to_value, "max_loan_to_value")?;
152        decimal_param_le_one(self.liquidation_threshold, "liquidation_threshold")?;
153        assert_lqt_gt_max_ltv(self.max_loan_to_value, self.liquidation_threshold)?;
154
155        self.liquidation_bonus.validate()?;
156        decimal_param_lt_one(self.protocol_liquidation_fee, "protocol_liquidation_fee")?;
157
158        if let Some(hls) = self.credit_manager.hls.as_ref() {
159            decimal_param_lt_one(hls.max_loan_to_value, "hls_max_loan_to_value")?;
160            decimal_param_le_one(hls.liquidation_threshold, "hls_liquidation_threshold")?;
161            assert_hls_lqt_gt_max_ltv(hls.max_loan_to_value, hls.liquidation_threshold)?;
162        }
163
164        let hls = self.credit_manager.hls.as_ref().map(|hls| hls.check(api)).transpose()?;
165
166        Ok(AssetParams {
167            denom: self.denom.clone(),
168            credit_manager: CmSettings {
169                whitelisted: self.credit_manager.whitelisted,
170                hls,
171            },
172            red_bank: self.red_bank.clone(),
173            max_loan_to_value: self.max_loan_to_value,
174            liquidation_threshold: self.liquidation_threshold,
175            liquidation_bonus: self.liquidation_bonus.clone(),
176            protocol_liquidation_fee: self.protocol_liquidation_fee,
177        })
178    }
179}