abstract_os/objects/
proxy_asset.rs

1//! # Proxy Asset
2//! Proxy assets are objects that describe an asset and a way to calculate that asset's value against a base asset.
3//!
4//! ## Details
5//! A proxy asset is composed of two components.
6//! * The `asset`, which is an [`AssetEntry`] and maps to an [`AssetInfo`].
7//! * The [`ValueRef`] which is an enum that indicates how to calculate the value for that asset.
8//!
9//! The base asset is the asset for which `value_reference` in `None`.
10//! **There should only be ONE base asset when configuring your proxy**
11
12use 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/// A proxy asset with unchecked ans_host entry fields.
35#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
36pub struct UncheckedProxyAsset {
37    /// The asset that's held by the proxy
38    pub asset: String,
39    /// The value reference provides the tooling to get the value of the asset
40    /// relative to the base asset.
41    /// If None, the provided asset is set as the base asset.
42    /// **You can only have one base asset!**
43    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    /// Perform checks on the proxy asset to ensure it can be resolved by the AnsHost
55    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/// Provides information on how to calculate the value of an asset
69#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
70
71pub enum UncheckedValueRef {
72    /// A pool address of an asset/asset pair
73    /// Both assets must be defined in the Proxy_assets state
74    Pool {
75        pair: String,
76        exchange: String,
77    },
78    // Liquidity Pool token
79    LiquidityToken {},
80    // a Proxy, the proxy also takes a Decimal (the multiplier)
81    // Asset will be valued as if they are Proxy tokens
82    ValueAs {
83        asset: String,
84        multiplier: Decimal,
85    },
86    // Query an external contract to get the value
87    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                // verify pair is available
112                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                // Ensure lp pair is registered
122                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/// Every ProxyAsset provides a way to determine its value recursively relative to
139/// a base asset.
140#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
141pub struct ProxyAsset {
142    /// Asset entry that maps to an AssetInfo using raw-queries on ans_host
143    pub asset: AssetEntry,
144    /// The value reference provides the tooling to get the value of the asset
145    /// relative to the base asset.
146    pub value_reference: Option<ValueRef>,
147}
148
149/// Provides information on how to calculate the value of an asset
150#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
151
152pub enum ValueRef {
153    /// A pool name of an asset/asset pair
154    /// Both assets must be defined in the Vault_assets state
155    Pool { pair: ContractEntry },
156    /// Liquidity pool token
157    LiquidityToken {},
158    /// Asset will be valued as if they are ValueAs.asset tokens
159    ValueAs {
160        asset: AssetEntry,
161        multiplier: Decimal,
162    },
163    /// Query an external contract to get the value
164    External { api_name: String },
165}
166
167impl ProxyAsset {
168    /// Calculates the value of the asset through the optionally provided ValueReference
169    // TODO: improve efficiency
170    // We could cache each asset/contract address and store each asset in a stack with the most complex (most hops) assets on top.
171    // Doing this would prevent an asset value from being calculated multiple times.
172    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        // Query how many of these tokens are held in the contract if not set.
180        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        // Is there a reference to calculate the value?
189        if let Some(value_reference) = self.value_reference.clone() {
190            match value_reference {
191                // A Pool refers to a swap pair that recursively leads to an asset/base_asset pool.
192                ValueRef::Pool { pair } => {
193                    return self.trade_pair_value(deps, env, ans_host, valued_asset, pair)
194                }
195                // Liquidity is an LP token, value() fn is called recursively on both assets in the pool
196                ValueRef::LiquidityToken {} => {
197                    // We map the LP token to its pair address.
198                    // lp tokens are stored as "dex/asset1_asset2" in the asset store.
199                    // pairs are stored as ContractEntry{protocol: dex, contract: asset1_asset2} in the contract store.
200                    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                // A proxy asset is used instead
205                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        // If there is no valueref, it means this token is the base token.
228        Ok(holding)
229    }
230
231    /// Calculates the value of an asset compared to some base asset through the provided trading pair.
232    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        // query assets held in pool, gives price
247        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        // other / this
255        let ratio = Decimal::from_ratio(pool_info.0.u128(), pool_info.1.u128());
256
257        // Get the value of the current asset in the denom of the other asset
258        let mut recursive_vault_asset = VAULT_ASSETS.load(deps.storage, &other_pool_asset)?;
259
260        // #other = #this * (pool_other/pool_this)
261        let amount_in_other_denom = valued_asset.amount * ratio;
262        // Call value on this other asset.
263        recursive_vault_asset.value(deps, env, ans_host, Some(amount_in_other_denom))
264    }
265
266    /// Calculate the value of an LP token
267    /// Uses the lp token name to query pair pool for both assets
268    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        // Get total supply of LP tokens and calculate share
284        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        // query assets held in pool, gives price
301        let (amount1, amount2) = (
302            asset_1.query_balance(&deps.querier, &pair_address)?,
303            asset_2.query_balance(&deps.querier, pair_address)?,
304        );
305
306        // load the assets
307        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        // set the amounts to the LP holdings
313        let vault_asset_1_amount = share * Uint128::new(amount1.u128());
314        let vault_asset_2_amount = share * Uint128::new(amount2.u128());
315        // Call value on these assets.
316        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    // Get the proxy asset
332    let mut replacement_vault_asset: ProxyAsset =
333        VAULT_ASSETS.load(deps.storage, &replacement_asset)?;
334    // call value on proxy asset with adjusted multiplier.
335    replacement_vault_asset.value(deps, env, ans_host, Some(holding * multiplier))
336}
337/// Get the other asset's name from a composite name
338/// ex: asset= "btc" composite = "btc_eth"
339/// returns "eth"
340pub 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
351/// Composite of form asset1_asset2
352pub 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}