1use cosmwasm_std::{
2 from_slice, to_binary, to_vec, Api, BankMsg, Coin, CosmosMsg, QuerierWrapper, StdError,
3 StdResult, Uint128, WasmMsg,
4};
5use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg};
6use cw_storage_plus::PrimaryKey;
7
8use crate::{asset::AssetInfo, Asset};
9
10impl PrimaryKey<'_> for AssetInfo {
11 type Prefix = ();
12
13 type SubPrefix = ();
14
15 fn key(&self) -> Vec<&[u8]> {
16 vec![self.as_bytes()]
17 }
18}
19
20impl<'a> PrimaryKey<'a> for &'a AssetInfo {
21 type Prefix = ();
22
23 type SubPrefix = ();
24
25 fn key(&self) -> Vec<&[u8]> {
26 vec![self.as_bytes()]
27 }
28}
29
30impl Asset {
31 pub fn new<A: Into<Uint128>>(info: AssetInfo, amount: A) -> Self {
32 Asset {
33 info,
34 amount: amount.into(),
35 }
36 }
37
38 pub fn is_empty(&self) -> bool {
39 self.amount.is_zero()
40 }
41
42 pub fn transfer_all_msg<T: Into<String>>(&self, to_address: T) -> StdResult<CosmosMsg> {
43 self.info.transfer_msg(to_address, self.amount)
44 }
45
46 pub fn assert_sent_token(&self, coins: &[Coin]) -> StdResult<()> {
47 if let AssetInfo::NativeToken { denom } = &self.info {
48 match coins.iter().find(|c| &c.denom == denom) {
49 Some(c) => (c.amount == self.amount).then(|| ()).ok_or_else(|| {
50 StdError::generic_err(format!(
51 "Expected amount {} but found {} for denom {}",
52 self.amount, c.amount, denom
53 ))
54 }),
55 None => Err(StdError::generic_err(format!(
56 "Denom {} not found in sent coins",
57 denom
58 ))),
59 }?;
60 }
61
62 Ok(())
63 }
64}
65
66impl AssetInfo {
67 pub fn assert_eq(&self, other: &Self) -> StdResult<()> {
68 (self == other)
69 .then(|| ())
70 .ok_or_else(|| StdError::generic_err("Asset info mismatch"))
71 }
72
73 pub fn as_bytes(&self) -> &[u8] {
74 match &self {
75 AssetInfo::Token { contract_addr } => contract_addr.as_bytes(),
76 AssetInfo::NativeToken { denom } => denom.as_bytes(),
77 }
78 }
79
80 pub fn from_bytes(b: &[u8], api: &dyn Api) -> StdResult<Self> {
81 let s = String::from_utf8(b.to_vec())
82 .map_err(|_| StdError::invalid_utf8("String parsing error"))?;
83 Ok(match api.addr_validate(&s) {
84 Ok(addr) => AssetInfo::Token {
85 contract_addr: addr,
86 },
87 Err(_) => AssetInfo::NativeToken { denom: s },
88 })
89 }
90
91 pub fn to_serde_vec(&self) -> StdResult<Vec<u8>> {
92 to_vec(self)
93 }
94
95 pub fn from_serde_slice(b: &[u8]) -> StdResult<Self> {
96 from_slice(b)
97 }
98
99 pub fn transfer_msg<T: Into<String>, A: Into<Uint128>>(
100 &self,
101 to_address: T,
102 amount: A,
103 ) -> StdResult<CosmosMsg> {
104 let msg = match self {
105 AssetInfo::Token { contract_addr } => CosmosMsg::Wasm(WasmMsg::Execute {
106 contract_addr: contract_addr.into(),
107 msg: to_binary(&Cw20ExecuteMsg::Transfer {
108 recipient: to_address.into(),
109 amount: amount.into(),
110 })?,
111 funds: vec![],
112 }),
113 AssetInfo::NativeToken { denom } => CosmosMsg::Bank(BankMsg::Send {
114 to_address: to_address.into(),
115 amount: vec![Coin {
116 denom: denom.clone(),
117 amount: amount.into(),
118 }],
119 }),
120 };
121
122 Ok(msg)
123 }
124
125 pub fn query_balance<T: Into<String>>(
126 &self,
127 querier: &QuerierWrapper,
128 address: T,
129 ) -> StdResult<Uint128> {
130 match self {
131 AssetInfo::Token { contract_addr } => {
132 let bal: BalanceResponse = querier.query_wasm_smart(
133 contract_addr,
134 &Cw20QueryMsg::Balance {
135 address: address.into(),
136 },
137 )?;
138 Ok(bal.balance)
139 }
140 AssetInfo::NativeToken { denom } => Ok(querier.query_balance(address, denom)?.amount),
141 }
142 }
143}