1use crate::contracts::{
3 cw20::entry::{
4 BalanceResponse as Cw20BalanceResponse, ExecuteMsg as Cw20ExecuteMsg,
5 QueryMsg as Cw20QueryMsg,
6 },
7 market::entry::ExecuteMsg as MarketExecuteMsg,
8};
9use crate::prelude::*;
10
11use cosmwasm_std::{
12 to_json_binary, Addr, BankMsg, Coin, CosmosMsg, Decimal256, QuerierWrapper, WasmMsg,
13};
14use serde::Serialize;
15
16#[cw_serde]
20pub enum TokenInit {
21 Cw20 {
23 addr: RawAddr,
25 },
26
27 Native {
29 denom: String,
31 decimal_places: u8,
33 },
34}
35
36impl From<Token> for TokenInit {
37 fn from(src: Token) -> Self {
38 match src {
39 Token::Native {
40 denom,
41 decimal_places,
42 } => Self::Native {
43 denom,
44 decimal_places,
45 },
46 Token::Cw20 { addr, .. } => Self::Cw20 { addr },
47 }
48 }
49}
50
51#[cw_serde]
59#[derive(Eq)]
60pub enum Token {
61 Cw20 {
63 addr: RawAddr,
65 decimal_places: u8,
67 },
68
69 Native {
71 denom: String,
73 decimal_places: u8,
75 },
76}
77
78impl Token {
79 pub(crate) fn name(&self) -> String {
80 match self {
81 Self::Native { denom, .. } => {
82 format!("native-{}", denom)
83 }
84 Self::Cw20 { addr, .. } => {
85 format!("cw20-{}", addr)
86 }
87 }
88 }
89
90 pub(crate) fn decimal_places(&self) -> u8 {
91 match self {
92 Self::Native { decimal_places, .. } => *decimal_places,
93 Self::Cw20 { decimal_places, .. } => *decimal_places,
94 }
95 }
96 pub fn into_transfer_msg(
100 &self,
101 recipient: &Addr,
102 amount: NonZero<Collateral>,
103 ) -> Result<Option<CosmosMsg>> {
104 match self {
105 Self::Native { .. } => {
106 let coin = self.into_native_coin(amount.into_number_gt_zero())?;
107
108 match coin {
109 Some(coin) => Ok(Some(CosmosMsg::Bank(BankMsg::Send {
110 to_address: recipient.to_string(),
111 amount: vec![coin],
112 }))),
113
114 None => Ok(None),
115 }
116 }
117 Self::Cw20 { addr, .. } => {
118 let msg = self.into_cw20_execute_transfer_msg(recipient, amount)?;
119
120 match msg {
121 Some(msg) => {
122 let msg = to_json_binary(&msg)?;
123
124 Ok(Some(CosmosMsg::Wasm(WasmMsg::Execute {
125 contract_addr: addr.to_string(),
126 msg,
127 funds: Vec::new(),
128 })))
129 }
130 None => Ok(None),
131 }
132 }
133 }
134 }
135
136 pub fn query_balance(&self, querier: &QuerierWrapper, user_addr: &Addr) -> Result<Collateral> {
139 self.query_balance_dec(querier, user_addr)
140 .map(Collateral::from_decimal256)
141 }
142
143 pub fn query_balance_dec(
146 &self,
147 querier: &QuerierWrapper,
148 user_addr: &Addr,
149 ) -> Result<Decimal256> {
150 self.from_u128(match self {
151 Self::Cw20 { addr, .. } => {
152 let resp: Cw20BalanceResponse = querier.query_wasm_smart(
153 addr.as_str(),
154 &Cw20QueryMsg::Balance {
155 address: user_addr.to_string().into(),
156 },
157 )?;
158
159 resp.balance.u128()
160 }
161 Self::Native { denom, .. } => {
162 let coin = querier.query_balance(user_addr, denom)?;
163 coin.amount.u128()
164 }
165 })
166 }
167
168 pub fn from_u128(&self, amount: u128) -> Result<Decimal256> {
175 Decimal256::from_atomics(amount, self.decimal_places().into()).map_err(|e| e.into())
176 }
177
178 pub fn into_u128(&self, amount: Decimal256) -> Result<Option<u128>> {
189 let value: u128 = amount
190 .into_number()
191 .to_u128_with_precision(self.decimal_places().into())
192 .ok_or_else(|| {
193 anyhow!(PerpError::new(
194 ErrorId::Conversion,
195 ErrorDomain::Wallet,
196 format!("{} unable to convert {amount} to u128!", self.name(),)
197 ))
198 })?;
199
200 if value > 0 {
201 Ok(Some(value))
202 } else {
203 Ok(None)
204 }
205 }
206
207 pub fn into_native_coin(&self, amount: NumberGtZero) -> Result<Option<Coin>> {
212 match self {
213 Self::Native { denom, .. } => {
214 Ok(self
215 .into_u128(amount.into_decimal256())?
216 .map(|amount| Coin {
217 denom: denom.clone(),
218 amount: amount.into(),
219 }))
220 }
221 Self::Cw20 { .. } => Err(anyhow!(PerpError::new(
222 ErrorId::NativeFunds,
223 ErrorDomain::Wallet,
224 format!("{} cannot be turned into a native coin", self.name())
225 ))),
226 }
227 }
228
229 pub fn round_down_to_precision(&self, amount: Collateral) -> Result<Collateral> {
231 self.from_u128(
232 self.into_u128(amount.into_decimal256())?
233 .unwrap_or_default(),
234 )
235 .map(Collateral::from_decimal256)
236 }
237
238 pub fn into_cw20_execute_send_msg<T: Serialize>(
243 &self,
244 contract: &Addr,
245 amount: Collateral,
246 submsg: &T,
247 ) -> Result<Option<Cw20ExecuteMsg>> {
248 match self {
249 Self::Native { .. } => Err(anyhow!(PerpError::new(
250 ErrorId::Cw20Funds,
251 ErrorDomain::Wallet,
252 format!("{} cannot be turned into a cw20 message", self.name())
253 ))),
254 Self::Cw20 { .. } => {
255 let msg = to_json_binary(submsg)?;
256 Ok(self
257 .into_u128(amount.into_decimal256())?
258 .map(|amount| Cw20ExecuteMsg::Send {
259 contract: contract.into(),
260 amount: amount.into(),
261 msg,
262 }))
263 }
264 }
265 }
266
267 pub fn into_cw20_execute_transfer_msg(
272 &self,
273 recipient: &Addr,
274 amount: NonZero<Collateral>,
275 ) -> Result<Option<Cw20ExecuteMsg>> {
276 match self {
277 Self::Native { .. } => Err(anyhow!(PerpError::new(
278 ErrorId::Cw20Funds,
279 ErrorDomain::Wallet,
280 format!("{} cannot be turned into a cw20 message", self.name())
281 ))),
282 Self::Cw20 { .. } => Ok(self.into_u128(amount.into_decimal256())?.map(|amount| {
283 Cw20ExecuteMsg::Transfer {
284 recipient: recipient.into(),
285 amount: amount.into(),
286 }
287 })),
288 }
289 }
290
291 pub fn into_market_execute_msg(
293 &self,
294 market_addr: &Addr,
295 amount: Collateral,
296 execute_msg: MarketExecuteMsg,
297 ) -> Result<WasmMsg> {
298 self.into_execute_msg(market_addr, amount, &execute_msg)
299 }
300
301 pub fn into_execute_msg<T: Serialize + std::fmt::Debug>(
303 &self,
304 contract_addr: &Addr,
305 amount: Collateral,
306 execute_msg: &T,
307 ) -> Result<WasmMsg> {
308 match self.clone() {
309 Self::Cw20 { addr, .. } => {
310 let msg = self
311 .into_cw20_execute_send_msg(contract_addr, amount, &execute_msg)
312 .map_err(|err| {
313 let downcast = err
314 .downcast_ref::<PerpError>()
315 .map(|item| item.description.clone());
316 let msg = format!("{downcast:?} (exec inner msg: {execute_msg:?})!");
317 anyhow!(PerpError::new(
318 ErrorId::Conversion,
319 ErrorDomain::Wallet,
320 msg
321 ))
322 })?;
323
324 match msg {
325 Some(msg) => Ok(WasmMsg::Execute {
326 contract_addr: addr.into_string(),
327 msg: to_json_binary(&msg)?,
328 funds: Vec::new(),
329 }),
330 None => {
331 Ok(WasmMsg::Execute {
334 contract_addr: contract_addr.to_string(),
335 msg: to_json_binary(&execute_msg)?,
336 funds: Vec::new(),
337 })
338 }
339 }
340 }
341 Self::Native { .. } => {
342 let funds = if amount.is_zero() {
343 Vec::new()
344 } else {
345 let amount = NumberGtZero::new(amount.into_decimal256())
346 .context("Unable to convert amount into NumberGtZero")?;
347 let coin = self
348 .into_native_coin(amount)
349 .map_err(|err| {
350 let downcast = err
351 .downcast_ref::<PerpError>()
352 .map(|item| item.description.clone());
353 let msg = format!("{downcast:?} (exec inner msg: {execute_msg:?})!");
354 anyhow!(PerpError::new(
355 ErrorId::Conversion,
356 ErrorDomain::Wallet,
357 msg
358 ))
359 })?
360 .unwrap();
361
362 vec![coin]
363 };
364
365 let execute_msg = to_json_binary(&execute_msg)?;
366
367 Ok(WasmMsg::Execute {
368 contract_addr: contract_addr.to_string(),
369 msg: execute_msg,
370 funds,
371 })
372 }
373 }
374 }
375
376 pub fn validate_collateral(&self, value: NonZero<Collateral>) -> Result<NonZero<Collateral>> {
379 let value_decimal256 = value.into_decimal256();
380
381 if let Some(value_128) = self.into_u128(value_decimal256)? {
382 let value_truncated = self.from_u128(value_128)?;
383 if value_truncated == value_decimal256 {
384 return Ok(value);
385 }
386 }
387
388 let msg = format!("Token Collateral must be as precise as the Token (is {}, only {} decimal places supported)", value, self.decimal_places());
389 Err(anyhow!(PerpError::new(
390 ErrorId::Conversion,
391 ErrorDomain::Wallet,
392 msg
393 )))
394 }
395}