1use super::{
13 ans_host::AnsHost,
14 asset_entry::AssetEntry,
15 contract_entry::{ContractEntry, UncheckedContractEntry},
16};
17use crate::{
18 error::AbstractOsError,
19 manager::state::OS_MODULES,
20 proxy::{
21 state::{ADMIN, VAULT_ASSETS},
22 ExternalValueResponse, ValueQueryMsg,
23 },
24 AbstractResult,
25};
26use cosmwasm_std::{
27 to_binary, Addr, Decimal, Deps, Env, QuerierWrapper, QueryRequest, StdError, Uint128, WasmQuery,
28};
29use cw_asset::{Asset, AssetInfo};
30use schemars::JsonSchema;
31use serde::{Deserialize, Serialize};
32use std::convert::TryInto;
33
34#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
36pub struct UncheckedProxyAsset {
37 pub asset: String,
39 pub value_reference: Option<UncheckedValueRef>,
44}
45
46impl UncheckedProxyAsset {
47 pub fn new(asset: impl Into<String>, value_reference: Option<UncheckedValueRef>) -> Self {
48 Self {
49 asset: asset.into(),
50 value_reference,
51 }
52 }
53
54 pub fn check(self, deps: Deps, ans_host: &AnsHost) -> AbstractResult<ProxyAsset> {
56 let entry: AssetEntry = self.asset.into();
57 ans_host.query_asset(&deps.querier, &entry)?;
58 let value_reference = self
59 .value_reference
60 .map(|val| val.check(deps, ans_host, &entry));
61 Ok(ProxyAsset {
62 asset: entry,
63 value_reference: value_reference.transpose()?,
64 })
65 }
66}
67
68#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
70
71pub enum UncheckedValueRef {
72 Pool {
75 pair: String,
76 exchange: String,
77 },
78 LiquidityToken {},
80 ValueAs {
83 asset: String,
84 multiplier: Decimal,
85 },
86 External {
88 api_name: String,
89 },
90}
91
92impl UncheckedValueRef {
93 pub fn check(
94 self,
95 deps: Deps,
96 ans_host: &AnsHost,
97 entry: &AssetEntry,
98 ) -> AbstractResult<ValueRef> {
99 match self {
100 UncheckedValueRef::Pool { pair, exchange } => {
101 let lowercase = pair.to_ascii_lowercase();
102 let mut composite: Vec<&str> = lowercase.split('_').collect();
103 if composite.len() != 2 {
104 return Err(AbstractOsError::EntryFormattingError {
105 actual: entry.to_string(),
106 expected: "asset1_asset2".to_string(),
107 });
108 };
109 composite.sort();
110 let pair_name = format!("{}_{}", composite[0], composite[1]);
111 let pair_contract: ContractEntry =
113 UncheckedContractEntry::new(exchange, pair_name).check();
114 ans_host.query_contract(&deps.querier, &pair_contract)?;
115 Ok(ValueRef::Pool {
116 pair: pair_contract,
117 })
118 }
119 UncheckedValueRef::LiquidityToken {} => {
120 let maybe_pair: UncheckedContractEntry = entry.to_string().try_into()?;
121 ans_host.query_contract(&deps.querier, &maybe_pair.check())?;
123 Ok(ValueRef::LiquidityToken {})
124 }
125 UncheckedValueRef::ValueAs { asset, multiplier } => {
126 let replacement_asset: AssetEntry = asset.into();
127 ans_host.query_asset(&deps.querier, &replacement_asset)?;
128 Ok(ValueRef::ValueAs {
129 asset: replacement_asset,
130 multiplier,
131 })
132 }
133 UncheckedValueRef::External { api_name } => Ok(ValueRef::External { api_name }),
134 }
135 }
136}
137
138#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
141pub struct ProxyAsset {
142 pub asset: AssetEntry,
144 pub value_reference: Option<ValueRef>,
147}
148
149#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
151
152pub enum ValueRef {
153 Pool { pair: ContractEntry },
156 LiquidityToken {},
158 ValueAs {
160 asset: AssetEntry,
161 multiplier: Decimal,
162 },
163 External { api_name: String },
165}
166
167impl ProxyAsset {
168 pub fn value(
173 &mut self,
174 deps: Deps,
175 env: &Env,
176 ans_host: &AnsHost,
177 set_holding: Option<Uint128>,
178 ) -> AbstractResult<Uint128> {
179 let asset_info = ans_host.query_asset(&deps.querier, &self.asset)?;
181 let holding: Uint128 = match set_holding {
182 Some(setter) => setter,
183 None => asset_info.query_balance(&deps.querier, env.contract.address.clone())?,
184 };
185
186 let valued_asset = Asset::new(asset_info, holding);
187
188 if let Some(value_reference) = self.value_reference.clone() {
190 match value_reference {
191 ValueRef::Pool { pair } => {
193 return self.trade_pair_value(deps, env, ans_host, valued_asset, pair)
194 }
195 ValueRef::LiquidityToken {} => {
197 let maybe_pair: UncheckedContractEntry = self.asset.to_string().try_into()?;
201 let pair = maybe_pair.check();
202 return self.lp_value(deps, env, ans_host, valued_asset, pair);
203 }
204 ValueRef::ValueAs { asset, multiplier } => {
206 return value_as_value(deps, env, ans_host, asset, multiplier, holding)
207 }
208 ValueRef::External { api_name } => {
209 let manager = ADMIN.get(deps)?.unwrap();
210 let maybe_api_addr = OS_MODULES.query(&deps.querier, manager, &api_name)?;
211 if let Some(api_addr) = maybe_api_addr {
212 let response: ExternalValueResponse =
213 deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
214 contract_addr: api_addr.to_string(),
215 msg: to_binary(&ValueQueryMsg {
216 asset: self.asset.clone(),
217 amount: valued_asset.amount,
218 })?,
219 }))?;
220 return Ok(response.value);
221 } else {
222 return Err(AbstractOsError::ApiNotInstalled(api_name));
223 }
224 }
225 }
226 }
227 Ok(holding)
229 }
230
231 pub fn trade_pair_value(
233 &self,
234 deps: Deps,
235 env: &Env,
236 ans_host: &AnsHost,
237 valued_asset: Asset,
238 pair: ContractEntry,
239 ) -> AbstractResult<Uint128> {
240 let other_pool_asset: AssetEntry =
241 other_asset_name(self.asset.as_str(), &pair.contract)?.into();
242
243 let pair_address = ans_host.query_contract(&deps.querier, &pair)?;
244 let other_asset_info = ans_host.query_asset(&deps.querier, &other_pool_asset)?;
245
246 let pool_info = (
248 other_asset_info.query_balance(&deps.querier, &pair_address)?,
249 valued_asset
250 .info
251 .query_balance(&deps.querier, pair_address)?,
252 );
253
254 let ratio = Decimal::from_ratio(pool_info.0.u128(), pool_info.1.u128());
256
257 let mut recursive_vault_asset = VAULT_ASSETS.load(deps.storage, &other_pool_asset)?;
259
260 let amount_in_other_denom = valued_asset.amount * ratio;
262 recursive_vault_asset.value(deps, env, ans_host, Some(amount_in_other_denom))
264 }
265
266 pub fn lp_value(
269 &self,
270 deps: Deps,
271 env: &Env,
272 ans_host: &AnsHost,
273 lp_asset: Asset,
274 pair: ContractEntry,
275 ) -> AbstractResult<Uint128> {
276 let supply: Uint128;
277 if let AssetInfo::Cw20(addr) = &lp_asset.info {
278 supply = query_cw20_supply(&deps.querier, addr)?;
279 } else {
280 return Err(StdError::generic_err("Can't have a native LP token").into());
281 }
282
283 let share: Decimal = Decimal::from_ratio(lp_asset.amount, supply.u128());
285
286 let other_pool_asset_names = get_pair_asset_names(pair.contract.as_str());
287
288 if other_pool_asset_names.len() != 2 {
289 return Err(AbstractOsError::FormattingError {
290 object: "lp asset entry".into(),
291 expected: "with two '_' seperated asset names".into(),
292 actual: pair.to_string(),
293 });
294 }
295
296 let pair_address = ans_host.query_contract(&deps.querier, &pair)?;
297
298 let asset_1 = ans_host.query_asset(&deps.querier, &other_pool_asset_names[0].into())?;
299 let asset_2 = ans_host.query_asset(&deps.querier, &other_pool_asset_names[1].into())?;
300 let (amount1, amount2) = (
302 asset_1.query_balance(&deps.querier, &pair_address)?,
303 asset_2.query_balance(&deps.querier, pair_address)?,
304 );
305
306 let mut vault_asset_1: ProxyAsset =
308 VAULT_ASSETS.load(deps.storage, &other_pool_asset_names[0].into())?;
309 let mut vault_asset_2: ProxyAsset =
310 VAULT_ASSETS.load(deps.storage, &other_pool_asset_names[1].into())?;
311
312 let vault_asset_1_amount = share * Uint128::new(amount1.u128());
314 let vault_asset_2_amount = share * Uint128::new(amount2.u128());
315 Ok(
317 vault_asset_1.value(deps, env, ans_host, Some(vault_asset_1_amount))?
318 + vault_asset_2.value(deps, env, ans_host, Some(vault_asset_2_amount))?,
319 )
320 }
321}
322
323pub fn value_as_value(
324 deps: Deps,
325 env: &Env,
326 ans_host: &AnsHost,
327 replacement_asset: AssetEntry,
328 multiplier: Decimal,
329 holding: Uint128,
330) -> AbstractResult<Uint128> {
331 let mut replacement_vault_asset: ProxyAsset =
333 VAULT_ASSETS.load(deps.storage, &replacement_asset)?;
334 replacement_vault_asset.value(deps, env, ans_host, Some(holding * multiplier))
336}
337pub fn other_asset_name<'a>(asset: &'a str, composite: &'a str) -> AbstractResult<&'a str> {
341 composite
342 .split('_')
343 .find(|component| *component != asset)
344 .ok_or_else(|| AbstractOsError::FormattingError {
345 object: "lp asset key".to_string(),
346 expected: "with '_' as asset separator".to_string(),
347 actual: composite.to_string(),
348 })
349}
350
351pub fn get_pair_asset_names(composite: &str) -> Vec<&str> {
353 composite.split('_').collect()
354}
355
356fn query_cw20_supply(querier: &QuerierWrapper, contract_addr: &Addr) -> AbstractResult<Uint128> {
357 let response: cw20::TokenInfoResponse =
358 querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
359 contract_addr: contract_addr.into(),
360 msg: to_binary(&cw20::Cw20QueryMsg::TokenInfo {})?,
361 }))?;
362 Ok(response.total_supply)
363}