use super::{
ans_host::AnsHost,
asset_entry::AssetEntry,
contract_entry::{ContractEntry, UncheckedContractEntry},
};
use crate::{
error::AbstractOsError,
manager::state::OS_MODULES,
proxy::{
state::{ADMIN, VAULT_ASSETS},
ExternalValueResponse, ValueQueryMsg,
},
AbstractResult,
};
use cosmwasm_std::{
to_binary, Addr, Decimal, Deps, Env, QuerierWrapper, QueryRequest, StdError, Uint128, WasmQuery,
};
use cw_asset::{Asset, AssetInfo};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct UncheckedProxyAsset {
pub asset: String,
pub value_reference: Option<UncheckedValueRef>,
}
impl UncheckedProxyAsset {
pub fn new(asset: impl Into<String>, value_reference: Option<UncheckedValueRef>) -> Self {
Self {
asset: asset.into(),
value_reference,
}
}
pub fn check(self, deps: Deps, ans_host: &AnsHost) -> AbstractResult<ProxyAsset> {
let entry: AssetEntry = self.asset.into();
ans_host.query_asset(&deps.querier, &entry)?;
let value_reference = self
.value_reference
.map(|val| val.check(deps, ans_host, &entry));
Ok(ProxyAsset {
asset: entry,
value_reference: value_reference.transpose()?,
})
}
}
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub enum UncheckedValueRef {
Pool {
pair: String,
exchange: String,
},
LiquidityToken {},
ValueAs {
asset: String,
multiplier: Decimal,
},
External {
api_name: String,
},
}
impl UncheckedValueRef {
pub fn check(
self,
deps: Deps,
ans_host: &AnsHost,
entry: &AssetEntry,
) -> AbstractResult<ValueRef> {
match self {
UncheckedValueRef::Pool { pair, exchange } => {
let lowercase = pair.to_ascii_lowercase();
let mut composite: Vec<&str> = lowercase.split('_').collect();
if composite.len() != 2 {
return Err(AbstractOsError::EntryFormattingError {
actual: entry.to_string(),
expected: "asset1_asset2".to_string(),
});
};
composite.sort();
let pair_name = format!("{}_{}", composite[0], composite[1]);
let pair_contract: ContractEntry =
UncheckedContractEntry::new(exchange, pair_name).check();
ans_host.query_contract(&deps.querier, &pair_contract)?;
Ok(ValueRef::Pool {
pair: pair_contract,
})
}
UncheckedValueRef::LiquidityToken {} => {
let maybe_pair: UncheckedContractEntry = entry.to_string().try_into()?;
ans_host.query_contract(&deps.querier, &maybe_pair.check())?;
Ok(ValueRef::LiquidityToken {})
}
UncheckedValueRef::ValueAs { asset, multiplier } => {
let replacement_asset: AssetEntry = asset.into();
ans_host.query_asset(&deps.querier, &replacement_asset)?;
Ok(ValueRef::ValueAs {
asset: replacement_asset,
multiplier,
})
}
UncheckedValueRef::External { api_name } => Ok(ValueRef::External { api_name }),
}
}
}
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct ProxyAsset {
pub asset: AssetEntry,
pub value_reference: Option<ValueRef>,
}
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub enum ValueRef {
Pool { pair: ContractEntry },
LiquidityToken {},
ValueAs {
asset: AssetEntry,
multiplier: Decimal,
},
External { api_name: String },
}
impl ProxyAsset {
pub fn value(
&mut self,
deps: Deps,
env: &Env,
ans_host: &AnsHost,
set_holding: Option<Uint128>,
) -> AbstractResult<Uint128> {
let asset_info = ans_host.query_asset(&deps.querier, &self.asset)?;
let holding: Uint128 = match set_holding {
Some(setter) => setter,
None => asset_info.query_balance(&deps.querier, env.contract.address.clone())?,
};
let valued_asset = Asset::new(asset_info, holding);
if let Some(value_reference) = self.value_reference.clone() {
match value_reference {
ValueRef::Pool { pair } => {
return self.trade_pair_value(deps, env, ans_host, valued_asset, pair)
}
ValueRef::LiquidityToken {} => {
let maybe_pair: UncheckedContractEntry = self.asset.to_string().try_into()?;
let pair = maybe_pair.check();
return self.lp_value(deps, env, ans_host, valued_asset, pair);
}
ValueRef::ValueAs { asset, multiplier } => {
return value_as_value(deps, env, ans_host, asset, multiplier, holding)
}
ValueRef::External { api_name } => {
let manager = ADMIN.get(deps)?.unwrap();
let maybe_api_addr = OS_MODULES.query(&deps.querier, manager, &api_name)?;
if let Some(api_addr) = maybe_api_addr {
let response: ExternalValueResponse =
deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
contract_addr: api_addr.to_string(),
msg: to_binary(&ValueQueryMsg {
asset: self.asset.clone(),
amount: valued_asset.amount,
})?,
}))?;
return Ok(response.value);
} else {
return Err(AbstractOsError::ApiNotInstalled(api_name));
}
}
}
}
Ok(holding)
}
pub fn trade_pair_value(
&self,
deps: Deps,
env: &Env,
ans_host: &AnsHost,
valued_asset: Asset,
pair: ContractEntry,
) -> AbstractResult<Uint128> {
let other_pool_asset: AssetEntry =
other_asset_name(self.asset.as_str(), &pair.contract)?.into();
let pair_address = ans_host.query_contract(&deps.querier, &pair)?;
let other_asset_info = ans_host.query_asset(&deps.querier, &other_pool_asset)?;
let pool_info = (
other_asset_info.query_balance(&deps.querier, &pair_address)?,
valued_asset
.info
.query_balance(&deps.querier, pair_address)?,
);
let ratio = Decimal::from_ratio(pool_info.0.u128(), pool_info.1.u128());
let mut recursive_vault_asset = VAULT_ASSETS.load(deps.storage, &other_pool_asset)?;
let amount_in_other_denom = valued_asset.amount * ratio;
recursive_vault_asset.value(deps, env, ans_host, Some(amount_in_other_denom))
}
pub fn lp_value(
&self,
deps: Deps,
env: &Env,
ans_host: &AnsHost,
lp_asset: Asset,
pair: ContractEntry,
) -> AbstractResult<Uint128> {
let supply: Uint128;
if let AssetInfo::Cw20(addr) = &lp_asset.info {
supply = query_cw20_supply(&deps.querier, addr)?;
} else {
return Err(StdError::generic_err("Can't have a native LP token").into());
}
let share: Decimal = Decimal::from_ratio(lp_asset.amount, supply.u128());
let other_pool_asset_names = get_pair_asset_names(pair.contract.as_str());
if other_pool_asset_names.len() != 2 {
return Err(AbstractOsError::FormattingError {
object: "lp asset entry".into(),
expected: "with two '_' seperated asset names".into(),
actual: pair.to_string(),
});
}
let pair_address = ans_host.query_contract(&deps.querier, &pair)?;
let asset_1 = ans_host.query_asset(&deps.querier, &other_pool_asset_names[0].into())?;
let asset_2 = ans_host.query_asset(&deps.querier, &other_pool_asset_names[1].into())?;
let (amount1, amount2) = (
asset_1.query_balance(&deps.querier, &pair_address)?,
asset_2.query_balance(&deps.querier, pair_address)?,
);
let mut vault_asset_1: ProxyAsset =
VAULT_ASSETS.load(deps.storage, &other_pool_asset_names[0].into())?;
let mut vault_asset_2: ProxyAsset =
VAULT_ASSETS.load(deps.storage, &other_pool_asset_names[1].into())?;
let vault_asset_1_amount = share * Uint128::new(amount1.u128());
let vault_asset_2_amount = share * Uint128::new(amount2.u128());
Ok(
vault_asset_1.value(deps, env, ans_host, Some(vault_asset_1_amount))?
+ vault_asset_2.value(deps, env, ans_host, Some(vault_asset_2_amount))?,
)
}
}
pub fn value_as_value(
deps: Deps,
env: &Env,
ans_host: &AnsHost,
replacement_asset: AssetEntry,
multiplier: Decimal,
holding: Uint128,
) -> AbstractResult<Uint128> {
let mut replacement_vault_asset: ProxyAsset =
VAULT_ASSETS.load(deps.storage, &replacement_asset)?;
replacement_vault_asset.value(deps, env, ans_host, Some(holding * multiplier))
}
pub fn other_asset_name<'a>(asset: &'a str, composite: &'a str) -> AbstractResult<&'a str> {
composite
.split('_')
.find(|component| *component != asset)
.ok_or_else(|| AbstractOsError::FormattingError {
object: "lp asset key".to_string(),
expected: "with '_' as asset separator".to_string(),
actual: composite.to_string(),
})
}
pub fn get_pair_asset_names(composite: &str) -> Vec<&str> {
composite.split('_').collect()
}
fn query_cw20_supply(querier: &QuerierWrapper, contract_addr: &Addr) -> AbstractResult<Uint128> {
let response: cw20::TokenInfoResponse =
querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
contract_addr: contract_addr.into(),
msg: to_binary(&cw20::Cw20QueryMsg::TokenInfo {})?,
}))?;
Ok(response.total_supply)
}