From ef175829d898e950e5c980bbf3a159ef113a2d09 Mon Sep 17 00:00:00 2001 From: Jakub Date: Wed, 20 Dec 2023 15:43:10 +0100 Subject: [PATCH] Credit agency: Implement queries; work not done, missing implementations in shade --- contracts/lending/credit-agency/Cargo.toml | 2 + .../lending/credit-agency/src/contract.rs | 214 ++++++++++++++++-- 2 files changed, 199 insertions(+), 17 deletions(-) diff --git a/contracts/lending/credit-agency/Cargo.toml b/contracts/lending/credit-agency/Cargo.toml index 6b752a94..1d9962a8 100644 --- a/contracts/lending/credit-agency/Cargo.toml +++ b/contracts/lending/credit-agency/Cargo.toml @@ -8,8 +8,10 @@ crate-type = ["cdylib", "rlib"] [dependencies] cosmwasm-schema = "1.4.0" +either = "1.6" lending-utils = { version = "0.1.0", path = "../../../packages/lending_utils" } lend-token = { version = "0.1.0", path = "../token" } +lend-market = { version = "0.1.0", path = "../market_v2" } shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ "snip20", "storage_plus", diff --git a/contracts/lending/credit-agency/src/contract.rs b/contracts/lending/credit-agency/src/contract.rs index 2ca27a00..9be8aef7 100644 --- a/contracts/lending/credit-agency/src/contract.rs +++ b/contracts/lending/credit-agency/src/contract.rs @@ -18,6 +18,8 @@ use crate::{ }; use lending_utils::token::Token; +use either::Either; + #[cfg_attr(not(feature = "library"), shd_entry_point)] pub fn instantiate( deps: DepsMut, @@ -72,30 +74,208 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result to_binary(todo!() /*&CONFIG.load(deps.storage)?*/)?, - Market { market_token } => { - to_binary(todo!() /*&query::market(deps, &market_token)?*/)? - } + Configuration {} => to_binary(&CONFIG.load(deps.storage)?)?, + Market { market_token } => to_binary(&query::market(deps, &market_token)?)?, ListMarkets { start_after, limit } => { - to_binary( - todo!(), /*&query::list_markets(deps, start_after, limit)?*/ - )? - } - TotalCreditLine { account } => { - to_binary(todo!() /*&query::total_credit_line(deps, account)?*/)? + to_binary(&query::list_markets(deps, start_after, limit)?)? } + TotalCreditLine { account } => to_binary(&query::total_credit_line(deps, account)?)?, ListEnteredMarkets { account, start_after, limit, - } => to_binary( - todo!(), /*&query::entered_markets(deps, account, start_after, limit)?*/ - )?, - IsOnMarket { account, market } => to_binary( - todo!(), /*&query::is_on_market(deps, account, market)?*/ - )?, - Liquidation { account } => to_binary(todo!() /*&query::liquidation(deps, account)?*/)?, + } => to_binary(&query::entered_markets(deps, account, start_after, limit)?)?, + IsOnMarket { account, market } => to_binary(&query::is_on_market(deps, account, market)?)?, + Liquidation { account } => to_binary(&query::liquidation(deps, account)?)?, }; Ok(res) } + +mod query { + use shade_protocol::{ + c_std::{Order, StdResult}, + secret_storage_plus::Bound, + }; + + use lend_market::msg::{QueryMsg as MarketQueryMsg, TokensBalanceResponse}; + use lending_utils::{ + coin::Coin, + credit_line::{CreditLineResponse, CreditLineValues}, + }; + + use crate::{ + msg::{ + IsOnMarketResponse, LiquidationResponse, ListEnteredMarketsResponse, + ListMarketsResponse, MarketResponse, + }, + state::{ENTERED_MARKETS, MARKETS}, + }; + + use super::*; + + /// Returns the address of the market associated to the given `market_token`. Returns an error + /// if the market does not exists or is being created. + pub fn market(deps: Deps, market_token: &Token) -> Result { + let state = MARKETS + .may_load(deps.storage, market_token)? + .ok_or_else(|| ContractError::NoMarket(market_token.denom()))?; + + let addr = state + .to_addr() + .ok_or_else(|| ContractError::MarketCreating(market_token.denom()))?; + + Ok(MarketResponse { + market_token: market_token.to_owned(), + market: addr, + }) + } + + // settings for pagination + const MAX_LIMIT: u32 = 30; + const DEFAULT_LIMIT: u32 = 10; + + pub fn list_markets( + deps: Deps, + start_after: Option, + limit: Option, + ) -> Result { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start = start_after.as_ref().map(Bound::exclusive); + + let markets: StdResult> = MARKETS + .range(deps.storage, start, None, Order::Ascending) + .map(|m| { + let (market_token, market) = m?; + + let result = market.to_addr().map(|addr| MarketResponse { + market_token, + market: addr, + }); + + Ok(result) + }) + .filter_map(|m| m.transpose()) + .take(limit) + .collect(); + + Ok(ListMarketsResponse { markets: markets? }) + } + + /// Handler for `QueryMsg::TotalCreditLine` + /// Computes the sum of `CreditLineValues` for all markets the `address` is participating to. + pub fn total_credit_line( + deps: Deps, + account: String, + ) -> Result { + let common_token = CONFIG.load(deps.storage)?.common_token; + let markets = ENTERED_MARKETS + .may_load(deps.storage, &Addr::unchecked(&account))? + .unwrap_or_default(); + + let total_credit_line: CreditLineValues = markets + .into_iter() + .map(|market| { + let price_response: CreditLineResponse = deps.querier.query_wasm_smart( + market, + &MarketQueryMsg::CreditLine { + account: deps.api.addr_validate(&account)?, + }, + )?; + let price_response = price_response.validate(&common_token.clone())?; + Ok(price_response) + }) + .collect::, ContractError>>()? + .iter() + .sum(); + Ok(total_credit_line.make_response(common_token)) + } + + pub fn entered_markets( + deps: Deps, + account: String, + start_after: Option, + limit: Option, + ) -> Result { + let account = Addr::unchecked(account); + let markets = ENTERED_MARKETS + .may_load(deps.storage, &account)? + .unwrap_or_default() + .into_iter(); + + let markets = if let Some(start_after) = &start_after { + Either::Left( + markets + .skip_while(move |market| market != start_after) + .skip(1), + ) + } else { + Either::Right(markets) + }; + + let markets = markets.take(limit.unwrap_or(u32::MAX) as usize).collect(); + + Ok(ListEnteredMarketsResponse { markets }) + } + + pub fn is_on_market( + deps: Deps, + account: String, + market: String, + ) -> Result { + let account = Addr::unchecked(account); + let market = Addr::unchecked(market); + let markets = ENTERED_MARKETS + .may_load(deps.storage, &account)? + .unwrap_or_default(); + + Ok(IsOnMarketResponse { + participating: markets.contains(&market), + }) + } + + pub fn liquidation(deps: Deps, account: String) -> Result { + let account_addr = deps.api.addr_validate(&account)?; + + // check whether the given account actually has more debt then credit + let total_credit_line: CreditLineResponse = total_credit_line(deps, account.clone())?; + let can_liquidate = total_credit_line.debt > total_credit_line.credit_line; + + let markets = ENTERED_MARKETS + .may_load(deps.storage, &account_addr)? + .unwrap_or_default(); + + let market_data: Result, _> = markets + .into_iter() + .map(|market| -> Result<(Addr, Coin, Coin), ContractError> { + let token_balances: TokensBalanceResponse = deps.querier.query_wasm_smart( + &market, + &MarketQueryMsg::TokensBalance { + account: deps.api.addr_validate(&account)?, + }, + )?; + + Ok((market, token_balances.collateral, token_balances.debt)) + }) + .collect(); + let market_data = market_data?; + + let collateral: Vec<_> = market_data + .iter() + .filter(|(_, collateral, _)| !collateral.amount.is_zero()) + .cloned() + .map(|(market, collateral, _)| (market, collateral)) + .collect(); + let debt: Vec<_> = market_data + .into_iter() + .filter(|(_, _, debt)| !debt.amount.is_zero()) + .map(|(market, _, debt)| (market, debt)) + .collect(); + + Ok(LiquidationResponse { + can_liquidate, + debt, + collateral, + }) + } +}