diff --git a/src/asset.rs b/src/asset.rs index 50e78f4..ffcded3 100644 --- a/src/asset.rs +++ b/src/asset.rs @@ -10,9 +10,6 @@ use serde::{Deserialize, Serialize}; use super::asset_info::{AssetInfo, AssetInfoBase}; -#[cfg(feature = "terra")] -static DECIMAL_FRACTION: Uint128 = Uint128::new(1_000_000_000_000_000_000u128); - /// Represents a fungible asset with a known amount /// /// Each asset instance contains two values: [`info`], which specifies the asset's type (CW20 or @@ -98,22 +95,25 @@ impl From for AssetUnchecked { impl AssetUnchecked { /// Validate data contained in an _unchecked_ **asset** instnace, return a new _checked_ - /// **asset** instance + /// **asset** instance: + /// * For CW20 tokens, assert the contract address is valid + /// * For SDK coins, assert that the denom is included in a given whitelist; skip if the + /// whitelist is not provided /// /// ```rust /// use cosmwasm_std::{Addr, Api}; /// use cw_asset::{Asset, AssetUnchecked}; /// /// fn validate_asset(api: &dyn Api, asset_unchecked: &AssetUnchecked) { - /// match asset_unchecked.check(api) { + /// match asset_unchecked.check(api, Some(&["uatom", "uluna"])) { /// Ok(asset) => println!("asset is valid: {}", asset.to_string()), /// Err(err) => println!("asset is invalid! reason: {}", err) /// } /// } /// ``` - pub fn check(&self, api: &dyn Api) -> StdResult { + pub fn check(&self, api: &dyn Api, optional_whitelist: Option<&[&str]>) -> StdResult { Ok(Asset { - info: self.info.check(api)?, + info: self.info.check(api, optional_whitelist)?, amount: self.amount, }) } @@ -307,6 +307,7 @@ impl std::cmp::PartialEq for Asset { #[cfg(test)] mod tests { use super::*; + use crate::AssetInfoUnchecked; use cosmwasm_std::testing::MockApi; #[derive(Serialize)] @@ -367,13 +368,22 @@ mod tests { } #[test] - fn casting() { + fn checking() { let api = MockApi::default(); - let checked = Asset::cw20(Addr::unchecked("mock_token"), 123456u128); + let checked = Asset::cw20(Addr::unchecked("mock_token"), 12345u128); + let unchecked: AssetUnchecked = checked.clone().into(); + assert_eq!(unchecked.check(&api, None).unwrap(), checked); + + let checked = Asset::native("uusd", 12345u128); let unchecked: AssetUnchecked = checked.clone().into(); + assert_eq!(unchecked.check(&api, Some(&["uusd", "uluna", "uosmo"])).unwrap(), checked); - assert_eq!(unchecked.check(&api).unwrap(), checked); + let unchecked = AssetUnchecked::new(AssetInfoUnchecked::native("uatom"), 12345u128); + assert_eq!( + unchecked.check(&api, Some(&["uusd", "uluna", "uosmo"])), + Err(StdError::generic_err("invalid denom uatom; must be uusd|uluna|uosmo")), + ); } #[test] diff --git a/src/asset_info.rs b/src/asset_info.rs index ff6bfa2..238fdea 100644 --- a/src/asset_info.rs +++ b/src/asset_info.rs @@ -1,8 +1,8 @@ use std::fmt; use cosmwasm_std::{ - to_binary, Addr, Api, BalanceResponse, BankQuery, QuerierWrapper, QueryRequest, StdResult, - Uint128, WasmQuery, + to_binary, Addr, Api, BalanceResponse, BankQuery, QuerierWrapper, QueryRequest, StdError, + StdResult, Uint128, WasmQuery, }; use cw20::{BalanceResponse as Cw20BalanceResponse, Cw20QueryMsg}; @@ -67,25 +67,37 @@ impl From for AssetInfoUnchecked { impl AssetInfoUnchecked { /// Validate data contained in an _unchecked_ **asset info** instance; return a new _checked_ - /// **asset info** instance - /// + /// **asset info** instance: + /// * For CW20 tokens, assert the contract address is valid + /// * For SDK coins, assert that the denom is included in a given whitelist; skip if the + /// whitelist is not provided + /// /// ```rust /// use cosmwasm_std::{Addr, Api, StdResult}; /// use cw_asset::{AssetInfo, AssetInfoUnchecked}; /// /// fn validate_asset_info(api: &dyn Api, info_unchecked: &AssetInfoUnchecked) { - /// match info_unchecked.check(api) { + /// match info_unchecked.check(api, Some(&["uatom", "uluna"])) { /// Ok(info) => println!("asset info is valid: {}", info.to_string()), /// Err(err) => println!("asset is invalid! reason: {}", err), /// } /// } /// ``` - pub fn check(&self, api: &dyn Api) -> StdResult { + pub fn check(&self, api: &dyn Api, optional_whitelist: Option<&[&str]>) -> StdResult { Ok(match self { AssetInfoUnchecked::Cw20(contract_addr) => { AssetInfo::Cw20(api.addr_validate(contract_addr)?) } - AssetInfoUnchecked::Native(denom) => AssetInfo::Native(denom.clone()), + AssetInfoUnchecked::Native(denom) => { + if let Some(whitelist) = optional_whitelist { + if !whitelist.contains(&&denom[..]) { + return Err(StdError::generic_err( + format!("invalid denom {}; must be {}", denom, whitelist.join("|")) + )); + } + } + AssetInfo::Native(denom.clone()) + } }) } } @@ -259,8 +271,17 @@ mod test { let checked = AssetInfo::cw20(Addr::unchecked("mock_token")); let unchecked: AssetInfoUnchecked = checked.clone().into(); + assert_eq!(unchecked.check(&api, None).unwrap(), checked); + + let checked = AssetInfo::native("uusd"); + let unchecked: AssetInfoUnchecked = checked.clone().into(); + assert_eq!(unchecked.check(&api, Some(&["uusd", "uluna", "uosmo"])).unwrap(), checked); - assert_eq!(unchecked.check(&api).unwrap(), checked); + let unchecked = AssetInfoUnchecked::native("uatom"); + assert_eq!( + unchecked.check(&api, Some(&["uusd", "uluna", "uosmo"])), + Err(StdError::generic_err("invalid denom uatom; must be uusd|uluna|uosmo")), + ); } #[test] diff --git a/src/asset_list.rs b/src/asset_list.rs index 33ef8ec..d004310 100644 --- a/src/asset_list.rs +++ b/src/asset_list.rs @@ -34,22 +34,28 @@ impl From for AssetListUnchecked { impl AssetListUnchecked { /// Validate data contained in an _unchecked_ **asset list** instance, return a new _checked_ - /// **asset list** instance + /// **asset list** instance: + /// * For CW20 tokens, assert the contract address is valid + /// * For SDK coins, assert that the denom is included in a given whitelist; skip if the + /// whitelist is not provided /// /// ```rust /// use cosmwasm_std::{Addr, Api, StdResult}; /// use cw_asset::{Asset, AssetList, AssetUnchecked, AssetListUnchecked}; /// /// fn validate_assets(api: &dyn Api, list_unchecked: &AssetListUnchecked) { - /// match list_unchecked.check(api) { + /// match list_unchecked.check(api, Some(&["uatom", "uluna"])) { /// Ok(list) => println!("asset list is valid: {}", list.to_string()), /// Err(err) => println!("asset list is invalid! reason: {}", err), /// } /// } /// ``` - pub fn check(&self, api: &dyn Api) -> StdResult { + pub fn check(&self, api: &dyn Api, optional_whitelist: Option<&[&str]>) -> StdResult { Ok(AssetList::from( - self.0.iter().map(|asset| asset.check(api)).collect::>>()?, + self.0 + .iter() + .map(|asset| asset.check(api, optional_whitelist)) + .collect::>>()? )) } } @@ -478,13 +484,17 @@ mod tests { } #[test] - fn casting() { + fn checking() { let api = MockApi::default(); let checked = mock_list(); let unchecked: AssetListUnchecked = checked.clone().into(); - - assert_eq!(unchecked.check(&api).unwrap(), checked); + assert_eq!(unchecked.check(&api, None).unwrap(), checked.clone()); + assert_eq!(unchecked.check(&api, Some(&["uusd", "uluna"])).unwrap(), checked); + assert_eq!( + unchecked.check(&api, Some(&["uatom", "uosmo", "uscrt"])), + Err(StdError::generic_err("invalid denom uusd; must be uatom|uosmo|uscrt")), + ); } #[test] diff --git a/src/lib.rs b/src/lib.rs index f1846e9..296b13a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,9 +49,9 @@ //! //! ## Use in messages //! -//! [`Asset`] and [`AssetList`] each come with an _unchecked_ counterpart which contains unverified -//! addresses and implements traits that allow them to be serialized into JSON, so that they can be -//! directly used in Cosmos messages: +//! [`Asset`] and [`AssetList`] each comes with an _unchecked_ counterpart which contains unverified +//! addresses and/or denoms, and implements traits that allow them to be serialized into JSON, so +//! that they can be directly used in Cosmos messages: //! //! ```rust //! use serde::{Serialize, Deserialize}; @@ -74,9 +74,11 @@ //! ```rust //! use cosmwasm_std::{Api, StdResult}; //! use cw_asset::{Asset, AssetUnchecked}; +//! +//! const ACCEPTED_DENOMS: &[&str] = &["uatom", "uosmo", "uluna"]; //! //! fn validate_deposit(api: &dyn Api, asset_unchecked: AssetUnchecked) -> StdResult<()> { -//! let asset: Asset = asset_unchecked.check(api)?; +//! let asset: Asset = asset_unchecked.check(api, Some(ACCEPTED_DENOMS))?; //! Ok(()) //! } //! ```