use std::fmt;
use cosmwasm_std::{
to_binary, Addr, Api, BalanceResponse, BankQuery, QuerierWrapper, QueryRequest, StdResult,
Uint128, WasmQuery,
};
use cw20::{BalanceResponse as Cw20BalanceResponse, Cw20QueryMsg};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AssetInfoBase<T> {
Cw20(T), Native(String), }
pub type AssetInfoUnchecked = AssetInfoBase<String>;
pub type AssetInfo = AssetInfoBase<Addr>;
impl From<AssetInfo> for AssetInfoUnchecked {
fn from(asset_info: AssetInfo) -> Self {
match &asset_info {
AssetInfo::Cw20(contract_addr) => AssetInfoUnchecked::Cw20(contract_addr.into()),
AssetInfo::Native(denom) => AssetInfoUnchecked::Native(denom.clone()),
}
}
}
impl AssetInfoUnchecked {
pub fn check(&self, api: &dyn Api) -> StdResult<AssetInfo> {
Ok(match self {
AssetInfoUnchecked::Cw20(contract_addr) => {
AssetInfo::Cw20(api.addr_validate(contract_addr)?)
}
AssetInfoUnchecked::Native(denom) => AssetInfo::Native(denom.clone()),
})
}
}
impl fmt::Display for AssetInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AssetInfo::Cw20(contract_addr) => write!(f, "{}", contract_addr),
AssetInfo::Native(denom) => write!(f, "{}", denom),
}
}
}
impl AssetInfo {
pub fn cw20<A: Into<Addr>>(contract_addr: A) -> Self {
AssetInfo::Cw20(contract_addr.into())
}
pub fn native<A: Into<String>>(denom: A) -> Self {
AssetInfo::Native(denom.into())
}
pub fn query_balance<T: Into<String>>(
&self,
querier: &QuerierWrapper,
address: T,
) -> StdResult<Uint128> {
match self {
AssetInfo::Cw20(contract_addr) => {
let response: Cw20BalanceResponse =
querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
contract_addr: contract_addr.into(),
msg: to_binary(&Cw20QueryMsg::Balance {
address: address.into(),
})?,
}))?;
Ok(response.balance)
}
AssetInfo::Native(denom) => {
let response: BalanceResponse =
querier.query(&QueryRequest::Bank(BankQuery::Balance {
address: address.into(),
denom: denom.clone(),
}))?;
Ok(response.amount.amount)
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::testing::mock_dependencies;
#[test]
fn creating_instances() {
let info = AssetInfo::cw20(Addr::unchecked("mock_token"));
assert_eq!(info, AssetInfo::Cw20(Addr::unchecked("mock_token")));
let info = AssetInfo::native("uusd");
assert_eq!(info, AssetInfo::Native(String::from("uusd")));
}
#[test]
fn comparing() {
let uluna = AssetInfo::native("uluna");
let uusd = AssetInfo::native("uusd");
let astro = AssetInfo::cw20(Addr::unchecked("astro_token"));
let mars = AssetInfo::cw20(Addr::unchecked("mars_token"));
assert_eq!(uluna == uusd, false);
assert_eq!(uluna == astro, false);
assert_eq!(astro == mars, false);
assert_eq!(uluna == uluna.clone(), true);
assert_eq!(astro == astro.clone(), true);
}
#[test]
fn displaying() {
let info = AssetInfo::native("uusd");
assert_eq!(info.to_string(), String::from("uusd"));
let info = AssetInfo::cw20(Addr::unchecked("mock_token"));
assert_eq!(info.to_string(), String::from("mock_token"));
}
#[test]
fn checking() {
let deps = mock_dependencies();
let checked = AssetInfo::cw20(Addr::unchecked("mock_token"));
let unchecked: AssetInfoUnchecked = checked.clone().into();
assert_eq!(unchecked.check(deps.as_ref().api).unwrap(), checked);
}
}