1pub mod msgs {
2
3 use cosmwasm_schema::{cw_serde, QueryResponses};
4
5 use cosmwasm_std::{Addr, Decimal, Uint128};
6 use cw20::Cw20ReceiveMsg;
7
8 use crate::common::AssetType;
9
10 use super::definitions::{PoolConfig, PoolData};
11
12 #[cw_serde]
13 pub struct InstantiateMsg {
14 pub config: PoolConfig,
15 pub lp_type: AssetType,
16 pub code_id_cw20: Option<u64>,
17 }
18
19 #[cw_serde]
20 pub enum ExecuteMsg {
21 Deposit(DepositMsg),
22 Receive(Cw20ReceiveMsg),
23 Borrow(BorrowMsg),
24 Repay(RepayMsg),
25 Withdraw(WithdrawMsg),
26 CloseExpiredLoan(CloseExpiredLoanMsg),
27 Swap(SwapMsg),
28 UpdateConfig(Box<UpdateConfigMsg>),
29 }
30
31 #[cw_serde]
32 #[derive(QueryResponses)]
33 pub enum QueryMsg {
34 #[returns(PoolData)]
35 PoolData {},
36 #[returns(PoolConfig)]
37 PoolConfig {},
38 #[returns(LoanInfoResponse)]
39 LoanInfo { id: u64 },
40 #[returns(CurrentInterestsResponse)]
41 CurrentInterests,
42 }
43
44 #[cw_serde]
45 pub struct MigrateMsg {}
46
47 #[cw_serde]
48 pub enum Cw20HookMsg {
49 Deposit(DepositMsg),
50 Borrow(BorrowMsg),
51 Withdraw(WithdrawMsg),
52 }
53
54 #[cw_serde]
55 pub struct DepositMsg {}
56
57 #[cw_serde]
58 pub struct BorrowMsg {
59 pub duration: u64,
60 }
61
62 #[cw_serde]
63 pub struct RepayMsg {
64 pub loan_id: u64,
65 }
66
67 #[cw_serde]
68 pub struct WithdrawMsg {}
69
70 #[cw_serde]
71 pub struct CloseExpiredLoanMsg {
72 pub loan_id: u64,
73 }
74
75 #[cw_serde]
76 pub struct SwapMsg {}
77
78 #[cw_serde]
79 pub struct UpdateConfigMsg {
80 pub config: PoolConfig,
81 pub factory: Option<String>,
82 }
83
84 #[cw_serde]
85 pub struct LoanInfoResponse {
86 pub owner: Addr,
87 pub loan_amount: Uint128,
88 pub collateral_amount: Uint128,
89 pub expiration_timestamp: u64,
90 pub duration: u64,
91 }
92
93 #[cw_serde]
94 pub struct CurrentInterestsResponse {
95 pub borrow: Decimal,
96 pub provide: Decimal,
97 }
98}
99
100pub mod definitions {
101 use cosmwasm_schema::cw_serde;
102 use cosmwasm_std::{Addr, Decimal, StdError, StdResult, Timestamp, Uint128};
103 use cw_asset::AssetInfo;
104 use rhaki_cw_plus::{math::IntoDecimal, serde_value::IntoSerdeJsonString};
105
106 use crate::{
107 common::{PriceType, YEAR_IN_SECONDS},
108 traits::AssertOwner,
109 };
110
111 #[cw_serde]
112 pub struct PoolConfig {
113 pub collateral: AssetInfo,
114 pub core: AssetInfo,
115 pub price_type: PriceType,
116 pub interest_type: InterestType,
117 pub loan_duration_type: LoanDurationType,
118 pub initial_ltv: Decimal,
119 pub allow_early_close_uncollateralized: bool,
120 pub swap_info: SwapInfo,
121 pub lp_info: AssetInfo,
122 }
123
124 impl PoolConfig {
125 pub fn validate(&self) -> StdResult<()> {
126 if self.initial_ltv > Decimal::one() {
127 return Err(StdError::generic_err(
128 "initial_ltv must be lesser then or equal 1",
129 ));
130 }
131
132 match self.loan_duration_type {
133 LoanDurationType::Fixed(duration) => {
134 if duration == 0 {
135 return Err(StdError::generic_err(
136 "loan duration must be greater than 0",
137 ));
138 }
139 }
140 LoanDurationType::Variable { min, max, .. } => {
141 if min > max || max == 0 {
142 return Err(StdError::generic_err(
143 "variable loan min must be greater than max",
144 ));
145 }
146 }
147 }
148
149 self.swap_info.validate()?;
150
151 Ok(())
152 }
153 }
154
155 #[cw_serde]
156 pub enum InterestType {
157 Quadratic { a: Decimal, c: Decimal },
158 }
159
160 impl InterestType {
161 pub fn calculate_interest(&self, utilization_ratio: Decimal) -> Decimal {
163 match self {
164 Self::Quadratic { a, c } => {
165 (utilization_ratio.pow(2) * a + c) / 100_u128.into_decimal()
166 }
167 }
168 }
169 }
170
171 impl IntoSerdeJsonString for InterestType {}
172
173 #[cw_serde]
174 pub enum LoanDurationType {
175 Fixed(u64),
176 Variable {
177 min: u64,
178 max: u64,
179 interest_multiplier_at_max: Decimal,
180 },
181 }
182
183 impl IntoSerdeJsonString for LoanDurationType {}
184
185 impl LoanDurationType {
186 pub fn assert_duration(&self, duration: u64) -> StdResult<()> {
187 match self {
188 LoanDurationType::Fixed(fix) => {
189 if *fix != duration {
190 return Err(StdError::generic_err("InvalidDuration"));
191 }
192 }
193 LoanDurationType::Variable { min, max, .. } => {
194 if duration < *min || duration > *max {
195 return Err(StdError::generic_err("InvalidDuration"));
196 }
197 }
198 }
199
200 Ok(())
201 }
202
203 pub fn get_multiplier(&self, duration: u64) -> Decimal {
204 match self {
205 LoanDurationType::Fixed(..) => Decimal::one(),
206 LoanDurationType::Variable {
207 min,
208 max,
209 interest_multiplier_at_max,
210 } => {
211 let ratio = Decimal::from_ratio(duration - min, max - min);
212 Decimal::one() + (interest_multiplier_at_max - Decimal::one()) * ratio
213 }
214 }
215 }
216 }
217
218 #[cw_serde]
219 pub struct PoolData {
220 pub core_avaiable: Uint128,
221 pub core_locked: Uint128,
222 pub collateral_avaiable: Uint128,
223 pub collateral_locked: Uint128,
224 pub global_index: Decimal,
225 pub last_update: u64,
226 pub factory: Addr,
227 pub loan_counter: u64,
228 }
229
230 impl PoolData {
231 pub fn new(current_timestamp: Timestamp, factory: Addr) -> PoolData {
232 PoolData {
233 last_update: current_timestamp.seconds(),
234 core_avaiable: Uint128::zero(),
235 core_locked: Uint128::zero(),
236 collateral_avaiable: Uint128::zero(),
237 collateral_locked: Uint128::zero(),
238 global_index: Decimal::zero(),
239 factory,
240 loan_counter: 0,
241 }
242 }
243
244 pub fn total_value_in_core(&self, price_collateral_in_core: Decimal) -> Uint128 {
245 self.core_avaiable
246 + self.collateral_avaiable * price_collateral_in_core
247 + self.core_locked
248 }
254
255 pub fn utilization_ratio(&self) -> Decimal {
256 if self.core_locked + self.core_avaiable > Uint128::zero() {
257 Decimal::from_ratio(self.core_locked, self.core_locked + self.core_avaiable)
258 } else {
259 Decimal::zero()
260 }
261 }
262
263 pub fn update_global_index(&mut self, current_timestamp: &Timestamp, config: &PoolConfig) {
264 let interest = self.current_interest(config);
265
266 let year_passed = Decimal::from_ratio(
267 current_timestamp.seconds() - self.last_update,
268 YEAR_IN_SECONDS,
269 );
270
271 self.global_index += interest * year_passed;
272 self.last_update = current_timestamp.seconds()
273 }
274
275 pub fn current_interest(&self, config: &PoolConfig) -> Decimal {
276 config
277 .interest_type
278 .calculate_interest(self.utilization_ratio())
279 }
280 }
281
282 impl AssertOwner for PoolData {
283 fn get_admin(&self) -> Addr {
284 self.factory.clone()
285 }
286 }
287
288 #[cw_serde]
289 pub enum SwapInfo {
290 Disabled,
291 OnlyFromcore { fee: Decimal },
292 }
293
294 impl IntoSerdeJsonString for SwapInfo {}
295
296 impl SwapInfo {
297 pub fn assert_input(&self, input: AssetInfo, config: &PoolConfig) -> StdResult<()> {
298 match self {
299 SwapInfo::Disabled => return Err(StdError::generic_err("Swap not enable")),
300 SwapInfo::OnlyFromcore { .. } => {
301 if input != config.core {
302 return Err(StdError::generic_err(format!(
303 "Wrong asset, expected: {}, received: {}",
304 config.core, input
305 )));
306 }
307 }
308 }
309
310 Ok(())
311 }
312
313 pub fn validate(&self) -> StdResult<()> {
314 match self {
315 SwapInfo::Disabled => Ok(()),
316 SwapInfo::OnlyFromcore { fee } => {
317 if *fee > Decimal::one() {
318 return Err(StdError::generic_err(
319 "close_position_fee must be lesser then or equal 1",
320 ));
321 }
322 Ok(())
323 }
324 }
325 }
326
327 pub fn get_fee(&self) -> StdResult<Decimal> {
328 match self {
329 SwapInfo::Disabled => Err(StdError::generic_err("Swap not enable")),
330 SwapInfo::OnlyFromcore { fee } => Ok(*fee),
331 }
332 }
333 }
334}