pub mod msgs {
use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::{Addr, Decimal, Uint128};
use cw20::Cw20ReceiveMsg;
use crate::common::AssetType;
use super::definitions::{PoolConfig, PoolData};
#[cw_serde]
pub struct InstantiateMsg {
pub config: PoolConfig,
pub lp_type: AssetType,
pub code_id_cw20: Option<u64>,
}
#[cw_serde]
pub enum ExecuteMsg {
Deposit(DepositMsg),
Receive(Cw20ReceiveMsg),
Borrow(BorrowMsg),
Repay(RepayMsg),
Withdraw(WithdrawMsg),
CloseExpiredLoan(CloseExpiredLoanMsg),
Swap(SwapMsg),
UpdateConfig(Box<UpdateConfigMsg>),
}
#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
#[returns(PoolData)]
PoolData {},
#[returns(PoolConfig)]
PoolConfig {},
#[returns(LoanInfoResponse)]
LoanInfo { id: u64 },
#[returns(CurrentInterestsResponse)]
CurrentInterests,
}
#[cw_serde]
pub struct MigrateMsg {}
#[cw_serde]
pub enum Cw20HookMsg {
Deposit(DepositMsg),
Borrow(BorrowMsg),
Withdraw(WithdrawMsg),
}
#[cw_serde]
pub struct DepositMsg {}
#[cw_serde]
pub struct BorrowMsg {
pub duration: u64,
}
#[cw_serde]
pub struct RepayMsg {
pub loan_id: u64,
}
#[cw_serde]
pub struct WithdrawMsg {}
#[cw_serde]
pub struct CloseExpiredLoanMsg {
pub loan_id: u64,
}
#[cw_serde]
pub struct SwapMsg {}
#[cw_serde]
pub struct UpdateConfigMsg {
pub config: PoolConfig,
pub factory: Option<String>,
}
#[cw_serde]
pub struct LoanInfoResponse {
pub owner: Addr,
pub loan_amount: Uint128,
pub collateral_amount: Uint128,
pub expiration_timestamp: u64,
pub duration: u64,
}
#[cw_serde]
pub struct CurrentInterestsResponse {
pub borrow: Decimal,
pub provide: Decimal,
}
}
pub mod definitions {
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, Decimal, StdError, StdResult, Timestamp, Uint128};
use cw_asset::AssetInfo;
use rhaki_cw_plus::{math::IntoDecimal, serde_value::IntoSerdeJsonString};
use crate::{
common::{PriceType, YEAR_IN_SECONDS},
traits::AssertOwner,
};
#[cw_serde]
pub struct PoolConfig {
pub collateral: AssetInfo,
pub core: AssetInfo,
pub price_type: PriceType,
pub interest_type: InterestType,
pub loan_duration_type: LoanDurationType,
pub initial_ltv: Decimal,
pub allow_early_close_uncollateralized: bool,
pub swap_info: SwapInfo,
pub lp_info: AssetInfo,
}
impl PoolConfig {
pub fn validate(&self) -> StdResult<()> {
if self.initial_ltv > Decimal::one() {
return Err(StdError::generic_err(
"initial_ltv must be lesser then or equal 1",
));
}
match self.loan_duration_type {
LoanDurationType::Fixed(duration) => {
if duration == 0 {
return Err(StdError::generic_err(
"loan duration must be greater than 0",
));
}
}
LoanDurationType::Variable { min, max, .. } => {
if min > max || max == 0 {
return Err(StdError::generic_err(
"variable loan min must be greater than max",
));
}
}
}
self.swap_info.validate()?;
Ok(())
}
}
#[cw_serde]
pub enum InterestType {
Quadratic { a: Decimal, c: Decimal },
}
impl InterestType {
pub fn calculate_interest(&self, utilization_ratio: Decimal) -> Decimal {
match self {
Self::Quadratic { a, c } => {
(utilization_ratio.pow(2) * a + c) / 100_u128.into_decimal()
}
}
}
}
impl IntoSerdeJsonString for InterestType {}
#[cw_serde]
pub enum LoanDurationType {
Fixed(u64),
Variable {
min: u64,
max: u64,
interest_multiplier_at_max: Decimal,
},
}
impl IntoSerdeJsonString for LoanDurationType {}
impl LoanDurationType {
pub fn assert_duration(&self, duration: u64) -> StdResult<()> {
match self {
LoanDurationType::Fixed(fix) => {
if *fix != duration {
return Err(StdError::generic_err("InvalidDuration"));
}
}
LoanDurationType::Variable { min, max, .. } => {
if duration < *min || duration > *max {
return Err(StdError::generic_err("InvalidDuration"));
}
}
}
Ok(())
}
pub fn get_multiplier(&self, duration: u64) -> Decimal {
match self {
LoanDurationType::Fixed(..) => Decimal::one(),
LoanDurationType::Variable {
min,
max,
interest_multiplier_at_max,
} => {
let ratio = Decimal::from_ratio(duration - min, max - min);
Decimal::one() + (interest_multiplier_at_max - Decimal::one()) * ratio
}
}
}
}
#[cw_serde]
pub struct PoolData {
pub core_avaiable: Uint128,
pub core_locked: Uint128,
pub collateral_avaiable: Uint128,
pub collateral_locked: Uint128,
pub global_index: Decimal,
pub last_update: u64,
pub factory: Addr,
pub loan_counter: u64,
}
impl PoolData {
pub fn new(current_timestamp: Timestamp, factory: Addr) -> PoolData {
PoolData {
last_update: current_timestamp.seconds(),
core_avaiable: Uint128::zero(),
core_locked: Uint128::zero(),
collateral_avaiable: Uint128::zero(),
collateral_locked: Uint128::zero(),
global_index: Decimal::zero(),
factory,
loan_counter: 0,
}
}
pub fn total_value_in_core(&self, price_collateral_in_core: Decimal) -> Uint128 {
self.core_avaiable
+ self.collateral_avaiable * price_collateral_in_core
+ self.core_locked
}
pub fn utilization_ratio(&self) -> Decimal {
if self.core_locked + self.core_avaiable > Uint128::zero() {
Decimal::from_ratio(self.core_locked, self.core_locked + self.core_avaiable)
} else {
Decimal::zero()
}
}
pub fn update_global_index(&mut self, current_timestamp: &Timestamp, config: &PoolConfig) {
let interest = self.current_interest(config);
let year_passed = Decimal::from_ratio(
current_timestamp.seconds() - self.last_update,
YEAR_IN_SECONDS,
);
self.global_index += interest * year_passed;
self.last_update = current_timestamp.seconds()
}
pub fn current_interest(&self, config: &PoolConfig) -> Decimal {
config
.interest_type
.calculate_interest(self.utilization_ratio())
}
}
impl AssertOwner for PoolData {
fn get_admin(&self) -> Addr {
self.factory.clone()
}
}
#[cw_serde]
pub enum SwapInfo {
Disabled,
OnlyFromcore { fee: Decimal },
}
impl IntoSerdeJsonString for SwapInfo {}
impl SwapInfo {
pub fn assert_input(&self, input: AssetInfo, config: &PoolConfig) -> StdResult<()> {
match self {
SwapInfo::Disabled => return Err(StdError::generic_err("Swap not enable")),
SwapInfo::OnlyFromcore { .. } => {
if input != config.core {
return Err(StdError::generic_err(format!(
"Wrong asset, expected: {}, received: {}",
config.core, input
)));
}
}
}
Ok(())
}
pub fn validate(&self) -> StdResult<()> {
match self {
SwapInfo::Disabled => Ok(()),
SwapInfo::OnlyFromcore { fee } => {
if *fee > Decimal::one() {
return Err(StdError::generic_err(
"close_position_fee must be lesser then or equal 1",
));
}
Ok(())
}
}
}
pub fn get_fee(&self) -> StdResult<Decimal> {
match self {
SwapInfo::Disabled => Err(StdError::generic_err("Swap not enable")),
SwapInfo::OnlyFromcore { fee } => Ok(*fee),
}
}
}
}