mars-params 1.0.7

Contract storing the asset params for Credit Manager and Red Bank.
Documentation
use std::str::FromStr;

use cosmwasm_std::Decimal;
use mars_params::{
    error::ContractError::Validation,
    msg::AssetParamsUpdate,
    types::hls::{HlsAssetType, HlsParamsUnchecked},
};
use mars_utils::error::ValidationError::{InvalidDenom, InvalidParam};

use crate::helpers::{assert_err, default_asset_params, MockEnv};

pub mod helpers;

#[test]
fn denom_must_be_native() {
    let mut mock = MockEnv::new().build().unwrap();
    let denom = "AA".to_string(); // Invalid native denom length

    let res = mock.update_asset_params(
        &mock.query_owner(),
        AssetParamsUpdate::AddOrUpdate {
            params: default_asset_params(&denom),
        },
    );
    assert_err(
        res,
        Validation(InvalidDenom {
            reason: "Invalid denom length".to_string(),
        }),
    );
}

#[test]
fn max_ltv_less_than_one() {
    let mut mock = MockEnv::new().build().unwrap();
    let mut params = default_asset_params("denom_xyz");
    params.max_loan_to_value = Decimal::from_str("1.1235").unwrap();

    let res = mock.update_asset_params(
        &mock.query_owner(),
        AssetParamsUpdate::AddOrUpdate {
            params,
        },
    );
    assert_err(
        res,
        Validation(InvalidParam {
            param_name: "max_loan_to_value".to_string(),
            invalid_value: "1.1235".to_string(),
            predicate: "< 1".to_string(),
        }),
    );
}

#[test]
fn liquidation_threshold_less_than_or_equal_to_one() {
    let mut mock = MockEnv::new().build().unwrap();
    let mut params = default_asset_params("denom_xyz");
    params.liquidation_threshold = Decimal::from_str("1.1235").unwrap();

    let res = mock.update_asset_params(
        &mock.query_owner(),
        AssetParamsUpdate::AddOrUpdate {
            params,
        },
    );
    assert_err(
        res,
        Validation(InvalidParam {
            param_name: "liquidation_threshold".to_string(),
            invalid_value: "1.1235".to_string(),
            predicate: "<= 1".to_string(),
        }),
    );
}

#[test]
fn liq_threshold_gt_max_ltv() {
    let mut mock = MockEnv::new().build().unwrap();
    let mut params = default_asset_params("denom_xyz");
    params.liquidation_threshold = Decimal::from_str("0.5").unwrap();
    params.max_loan_to_value = Decimal::from_str("0.6").unwrap();

    let res = mock.update_asset_params(
        &mock.query_owner(),
        AssetParamsUpdate::AddOrUpdate {
            params,
        },
    );
    assert_err(
        res,
        Validation(InvalidParam {
            param_name: "liquidation_threshold".to_string(),
            invalid_value: "0.5".to_string(),
            predicate: "> 0.6 (max LTV)".to_string(),
        }),
    );
}

#[test]
fn hls_max_ltv_less_than_one() {
    let mut mock = MockEnv::new().build().unwrap();
    let mut params = default_asset_params("denom_xyz");
    params.credit_manager.hls = Some(HlsParamsUnchecked {
        max_loan_to_value: Decimal::from_str("1.1235").unwrap(),
        liquidation_threshold: Decimal::from_str("0.5").unwrap(),
        correlations: vec![],
    });

    let res = mock.update_asset_params(
        &mock.query_owner(),
        AssetParamsUpdate::AddOrUpdate {
            params,
        },
    );
    assert_err(
        res,
        Validation(InvalidParam {
            param_name: "hls_max_loan_to_value".to_string(),
            invalid_value: "1.1235".to_string(),
            predicate: "< 1".to_string(),
        }),
    );
}

#[test]
fn hls_liquidation_threshold_less_than_or_equal_to_one() {
    let mut mock = MockEnv::new().build().unwrap();
    let mut params = default_asset_params("denom_xyz");
    params.credit_manager.hls = Some(HlsParamsUnchecked {
        max_loan_to_value: Decimal::from_str("0.6").unwrap(),
        liquidation_threshold: Decimal::from_str("1.1235").unwrap(),
        correlations: vec![],
    });

    let res = mock.update_asset_params(
        &mock.query_owner(),
        AssetParamsUpdate::AddOrUpdate {
            params,
        },
    );
    assert_err(
        res,
        Validation(InvalidParam {
            param_name: "hls_liquidation_threshold".to_string(),
            invalid_value: "1.1235".to_string(),
            predicate: "<= 1".to_string(),
        }),
    );
}

#[test]
fn hls_liq_threshold_gt_hls_max_ltv() {
    let mut mock = MockEnv::new().build().unwrap();
    let mut params = default_asset_params("denom_xyz");
    params.credit_manager.hls = Some(HlsParamsUnchecked {
        max_loan_to_value: Decimal::from_str("0.6").unwrap(),
        liquidation_threshold: Decimal::from_str("0.5").unwrap(),
        correlations: vec![],
    });

    let res = mock.update_asset_params(
        &mock.query_owner(),
        AssetParamsUpdate::AddOrUpdate {
            params,
        },
    );
    assert_err(
        res,
        Validation(InvalidParam {
            param_name: "hls_liquidation_threshold".to_string(),
            invalid_value: "0.5".to_string(),
            predicate: "> 0.6 (hls max LTV)".to_string(),
        }),
    );
}

#[test]
fn correlations_must_be_valid_denoms() {
    let mut mock = MockEnv::new().build().unwrap();
    let mut params = default_asset_params("denom_xyz");
    params.credit_manager.hls = Some(HlsParamsUnchecked {
        max_loan_to_value: Decimal::from_str("0.5").unwrap(),
        liquidation_threshold: Decimal::from_str("0.7").unwrap(),
        correlations: vec![HlsAssetType::Coin {
            denom: "AA".to_string(),
        }],
    });

    let res = mock.update_asset_params(
        &mock.query_owner(),
        AssetParamsUpdate::AddOrUpdate {
            params,
        },
    );
    assert_err(
        res,
        Validation(InvalidDenom {
            reason: "Invalid denom length".to_string(),
        }),
    );
}

#[test]
fn protocol_liquidation_fee_less_than_one() {
    let mut mock = MockEnv::new().build().unwrap();
    let mut params = default_asset_params("denom_xyz");
    params.protocol_liquidation_fee = Decimal::from_str("1").unwrap();

    let res = mock.update_asset_params(
        &mock.query_owner(),
        AssetParamsUpdate::AddOrUpdate {
            params,
        },
    );
    assert_err(
        res,
        Validation(InvalidParam {
            param_name: "protocol_liquidation_fee".to_string(),
            invalid_value: "1".to_string(),
            predicate: "< 1".to_string(),
        }),
    );
}

#[test]
fn liquidation_bonus_param_b_out_of_range() {
    let mut mock = MockEnv::new().build().unwrap();
    let mut params = default_asset_params("denom_xyz");
    params.liquidation_bonus.starting_lb = Decimal::from_str("0.101").unwrap();

    let res = mock.update_asset_params(
        &mock.query_owner(),
        AssetParamsUpdate::AddOrUpdate {
            params,
        },
    );
    assert_err(
        res,
        Validation(InvalidParam {
            param_name: "starting_lb".to_string(),
            invalid_value: "0.101".to_string(),
            predicate: "[0, 0.1]".to_string(),
        }),
    );
}

#[test]
fn liquidation_bonus_param_slope_out_of_range() {
    let mut mock = MockEnv::new().build().unwrap();
    let mut params = default_asset_params("denom_xyz");

    params.liquidation_bonus.slope = Decimal::from_str("0.99").unwrap();
    let res = mock.update_asset_params(
        &mock.query_owner(),
        AssetParamsUpdate::AddOrUpdate {
            params: params.clone(),
        },
    );
    assert_err(
        res,
        Validation(InvalidParam {
            param_name: "slope".to_string(),
            invalid_value: "0.99".to_string(),
            predicate: "[1, 5]".to_string(),
        }),
    );

    params.liquidation_bonus.slope = Decimal::from_str("5.01").unwrap();
    let res = mock.update_asset_params(
        &mock.query_owner(),
        AssetParamsUpdate::AddOrUpdate {
            params,
        },
    );
    assert_err(
        res,
        Validation(InvalidParam {
            param_name: "slope".to_string(),
            invalid_value: "5.01".to_string(),
            predicate: "[1, 5]".to_string(),
        }),
    );
}

#[test]
fn liquidation_bonus_param_min_lb_out_of_range() {
    let mut mock = MockEnv::new().build().unwrap();
    let mut params = default_asset_params("denom_xyz");
    params.liquidation_bonus.min_lb = Decimal::from_str("0.101").unwrap();

    let res = mock.update_asset_params(
        &mock.query_owner(),
        AssetParamsUpdate::AddOrUpdate {
            params,
        },
    );
    assert_err(
        res,
        Validation(InvalidParam {
            param_name: "min_lb".to_string(),
            invalid_value: "0.101".to_string(),
            predicate: "[0, 0.1]".to_string(),
        }),
    );
}

#[test]
fn liquidation_bonus_param_max_lb_out_of_range() {
    let mut mock = MockEnv::new().build().unwrap();
    let mut params = default_asset_params("denom_xyz");

    params.liquidation_bonus.max_lb = Decimal::from_str("0.0499").unwrap();
    let res = mock.update_asset_params(
        &mock.query_owner(),
        AssetParamsUpdate::AddOrUpdate {
            params: params.clone(),
        },
    );
    assert_err(
        res,
        Validation(InvalidParam {
            param_name: "max_lb".to_string(),
            invalid_value: "0.0499".to_string(),
            predicate: "[0.05, 0.3]".to_string(),
        }),
    );

    params.liquidation_bonus.max_lb = Decimal::from_str("0.31").unwrap();
    let res = mock.update_asset_params(
        &mock.query_owner(),
        AssetParamsUpdate::AddOrUpdate {
            params,
        },
    );
    assert_err(
        res,
        Validation(InvalidParam {
            param_name: "max_lb".to_string(),
            invalid_value: "0.31".to_string(),
            predicate: "[0.05, 0.3]".to_string(),
        }),
    );
}

#[test]
fn liquidation_bonus_param_max_lb_gt_min_lb() {
    let mut mock = MockEnv::new().build().unwrap();
    let mut params = default_asset_params("denom_xyz");
    params.liquidation_bonus.min_lb = Decimal::from_str("0.08").unwrap();
    params.liquidation_bonus.max_lb = Decimal::from_str("0.07").unwrap();

    let res = mock.update_asset_params(
        &mock.query_owner(),
        AssetParamsUpdate::AddOrUpdate {
            params,
        },
    );
    assert_err(
        res,
        Validation(InvalidParam {
            param_name: "max_lb".to_string(),
            invalid_value: "0.07".to_string(),
            predicate: "> 0.08 (min LB)".to_string(),
        }),
    );
}