Skip to content

Commit

Permalink
more custom errors
Browse files Browse the repository at this point in the history
  • Loading branch information
larry0x committed Feb 2, 2023
1 parent 6550644 commit 848ed65
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 66 deletions.
100 changes: 60 additions & 40 deletions src/asset.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use std::{convert::TryFrom, fmt, str::FromStr};

use cosmwasm_schema::cw_serde;
use cosmwasm_std::{
to_binary, Addr, Api, BankMsg, Binary, Coin, CosmosMsg, StdError, StdResult, Uint128, WasmMsg,
};
use cosmwasm_std::{to_binary, Addr, Api, BankMsg, Binary, Coin, CosmosMsg, Uint128, WasmMsg};
use cw20::Cw20ExecuteMsg;
use cw_address_like::AddressLike;

Expand Down Expand Up @@ -175,7 +173,11 @@ impl AssetUnchecked {
/// }
/// }
/// ```
pub fn check(&self, api: &dyn Api, optional_whitelist: Option<&[&str]>) -> Result<Asset, AssetError> {
pub fn check(
&self,
api: &dyn Api,
optional_whitelist: Option<&[&str]>,
) -> Result<Asset, AssetError> {
Ok(Asset {
info: self.info.check(api, optional_whitelist)?,
amount: self.amount,
Expand Down Expand Up @@ -205,23 +207,24 @@ impl From<&Coin> for Asset {
}

impl TryFrom<Asset> for Coin {
type Error = StdError;
type Error = AssetError;

fn try_from(asset: Asset) -> Result<Self, Self::Error> {
match &asset.info {
AssetInfo::Native(denom) => Ok(Coin {
denom: denom.clone(),
amount: asset.amount,
}),
AssetInfo::Cw20(_) => Err(StdError::generic_err(format!(
"cannot cast asset {} into cosmwasm_std::Coin",
asset
))),
AssetInfo::Cw20(_) => Err(AssetError::CannotCastToStdCoin {
asset: asset.to_string(),
}),
}
}
}

impl TryFrom<&Asset> for Coin {
type Error = StdError;
type Error = AssetError;

fn try_from(asset: &Asset) -> Result<Self, Self::Error> {
Coin::try_from(asset.clone())
}
Expand Down Expand Up @@ -258,20 +261,22 @@ impl Asset {
/// MockCommand {},
/// }
///
/// use cosmwasm_std::{to_binary, Addr, Response, StdResult};
/// use cw_asset::Asset;
/// use cosmwasm_std::{to_binary, Addr, Response};
/// use cw_asset::{Asset, AssetError};
///
/// fn send_asset(
/// asset: &Asset,
/// contract_addr: &Addr,
/// msg: &MockReceiveMsg,
/// ) -> StdResult<Response> {
/// ) -> Result<Response, AssetError> {
/// let msg = asset.send_msg(contract_addr, to_binary(msg)?)?;
///
/// Ok(Response::new().add_message(msg).add_attribute("asset_sent", asset.to_string()))
/// Ok(Response::new()
/// .add_message(msg)
/// .add_attribute("asset_sent", asset.to_string()))
/// }
/// ```
pub fn send_msg<A: Into<String>>(&self, to: A, msg: Binary) -> StdResult<CosmosMsg> {
pub fn send_msg<A: Into<String>>(&self, to: A, msg: Binary) -> Result<CosmosMsg, AssetError> {
match &self.info {
AssetInfo::Cw20(contract_addr) => Ok(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: contract_addr.into(),
Expand All @@ -282,26 +287,28 @@ impl Asset {
})?,
funds: vec![],
})),
AssetInfo::Native(_) => {
Err(StdError::generic_err("native coins do not have `send` method"))
},
AssetInfo::Native(_) => Err(AssetError::UnavailableMethodForNative {
method: "send".into(),
}),
}
}

/// Generate a message that transfers the asset from the sender to to a
/// specified account.
///
/// ```rust
/// use cosmwasm_std::{Addr, Response, StdResult};
/// use cw_asset::Asset;
/// use cosmwasm_std::{Addr, Response};
/// use cw_asset::{Asset, AssetError};
///
/// fn transfer_asset(asset: &Asset, recipient_addr: &Addr) -> StdResult<Response> {
/// fn transfer_asset(asset: &Asset, recipient_addr: &Addr) -> Result<Response, AssetError> {
/// let msg = asset.transfer_msg(recipient_addr)?;
///
/// Ok(Response::new().add_message(msg).add_attribute("asset_sent", asset.to_string()))
/// Ok(Response::new()
/// .add_message(msg)
/// .add_attribute("asset_sent", asset.to_string()))
/// }
/// ```
pub fn transfer_msg<A: Into<String>>(&self, to: A) -> StdResult<CosmosMsg> {
pub fn transfer_msg<A: Into<String>>(&self, to: A) -> Result<CosmosMsg, AssetError> {
match &self.info {
AssetInfo::Native(denom) => Ok(CosmosMsg::Bank(BankMsg::Send {
to_address: to.into(),
Expand Down Expand Up @@ -329,20 +336,26 @@ impl Asset {
/// equivalent method implemented.
///
/// ```rust
/// use cosmwasm_std::{Addr, Response, StdResult};
/// use cw_asset::Asset;
/// use cosmwasm_std::{Addr, Response};
/// use cw_asset::{Asset, AssetError};
///
/// fn draw_asset(asset: &Asset, user_addr: &Addr, contract_addr: &Addr) -> StdResult<Response> {
/// fn draw_asset(
/// asset: &Asset,
/// user_addr: &Addr,
/// contract_addr: &Addr,
/// ) -> Result<Response, AssetError> {
/// let msg = asset.transfer_from_msg(user_addr, contract_addr)?;
///
/// Ok(Response::new().add_message(msg).add_attribute("asset_drawn", asset.to_string()))
/// Ok(Response::new()
/// .add_message(msg)
/// .add_attribute("asset_drawn", asset.to_string()))
/// }
/// ```
pub fn transfer_from_msg<A: Into<String>, B: Into<String>>(
&self,
from: A,
to: B,
) -> StdResult<CosmosMsg> {
) -> Result<CosmosMsg, AssetError> {
match &self.info {
AssetInfo::Cw20(contract_addr) => Ok(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: contract_addr.into(),
Expand All @@ -353,9 +366,9 @@ impl Asset {
})?,
funds: vec![],
})),
AssetInfo::Native(_) => {
Err(StdError::generic_err("native coins do not have `transfer_from` method"))
},
AssetInfo::Native(_) => Err(AssetError::UnavailableMethodForNative {
method: "transfer_from".into(),
}),
}
}
}
Expand All @@ -366,7 +379,7 @@ impl Asset {

#[cfg(test)]
mod tests {
use cosmwasm_std::testing::MockApi;
use cosmwasm_std::{testing::MockApi, StdError};
use serde::Serialize;

use super::*;
Expand Down Expand Up @@ -421,15 +434,15 @@ mod tests {
let astro = Asset::cw20(Addr::unchecked("astro_token"), 69u128);
assert_eq!(
Coin::try_from(&astro),
Err(StdError::generic_err(
"cannot cast asset cw20:astro_token:69 into cosmwasm_std::Coin"
)),
Err(AssetError::CannotCastToStdCoin {
asset: "cw20:astro_token:69".into(),
}),
);
assert_eq!(
Coin::try_from(astro),
Err(StdError::generic_err(
"cannot cast asset cw20:astro_token:69 into cosmwasm_std::Coin"
)),
Err(AssetError::CannotCastToStdCoin {
asset: "cw20:astro_token:69".into(),
}),
);
}

Expand Down Expand Up @@ -607,7 +620,12 @@ mod tests {
);

let err = coin.send_msg("mock_contract", bin_msg);
assert_eq!(err, Err(StdError::generic_err("native coins do not have `send` method")));
assert_eq!(
err,
Err(AssetError::UnavailableMethodForNative {
method: "send".into(),
}),
);

let msg = token.transfer_msg("alice").unwrap();
assert_eq!(
Expand Down Expand Up @@ -649,7 +667,9 @@ mod tests {
let err = coin.transfer_from_msg("bob", "charlie");
assert_eq!(
err,
Err(StdError::generic_err("native coins do not have `transfer_from` method"))
Err(AssetError::UnavailableMethodForNative {
method: "transfer_from".into(),
}),
);
}
}
50 changes: 32 additions & 18 deletions src/asset_list.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{fmt, str::FromStr};

use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, Api, Coin, CosmosMsg, StdError, StdResult};
use cosmwasm_std::{Addr, Api, Coin, CosmosMsg, StdResult};
use cw_address_like::AddressLike;

use crate::{Asset, AssetBase, AssetError, AssetInfo, AssetUnchecked};
Expand Down Expand Up @@ -377,16 +377,15 @@ impl AssetList {
///
/// let len = list.len(); // should be zero, as uluna is purged from the list
/// ```
pub fn deduct(&mut self, asset_to_deduct: &Asset) -> StdResult<&mut Self> {
pub fn deduct(&mut self, asset_to_deduct: &Asset) -> Result<&mut Self, AssetError> {
match self.0.iter_mut().find(|asset| asset.info == asset_to_deduct.info) {
Some(asset) => {
asset.amount = asset.amount.checked_sub(asset_to_deduct.amount)?;
},
None => {
return Err(StdError::generic_err(format!(
"not found in asset list: {}",
asset_to_deduct.info
)));
return Err(AssetError::NotFoundInList {
info: asset_to_deduct.info.to_string(),
});
},
}
Ok(self.purge())
Expand Down Expand Up @@ -414,7 +413,7 @@ impl AssetList {
///
/// let len = list.len(); // should be zero, as uusd is purged from the list
/// ```
pub fn deduct_many(&mut self, assets_to_deduct: &AssetList) -> StdResult<&mut Self> {
pub fn deduct_many(&mut self, assets_to_deduct: &AssetList) -> Result<&mut Self, AssetError> {
for asset in &assets_to_deduct.0 {
self.deduct(asset)?;
}
Expand All @@ -424,20 +423,28 @@ impl AssetList {
/// Generate a transfer messages for every asset in the list
///
/// ```rust
/// use cosmwasm_std::{Addr, Response, StdResult};
/// use cw_asset::AssetList;
/// use cosmwasm_std::{Addr, Response};
/// use cw_asset::{AssetError, AssetList};
///
/// fn transfer_assets(list: &AssetList, recipient_addr: &Addr) -> StdResult<Response> {
/// fn transfer_assets(
/// list: &AssetList,
/// recipient_addr: &Addr,
/// ) -> Result<Response, AssetError> {
/// let msgs = list.transfer_msgs(recipient_addr)?;
///
/// Ok(Response::new().add_messages(msgs).add_attribute("assets_sent", list.to_string()))
/// Ok(Response::new()
/// .add_messages(msgs)
/// .add_attribute("assets_sent", list.to_string()))
/// }
/// ```
pub fn transfer_msgs<A: Into<String> + Clone>(&self, to: A) -> StdResult<Vec<CosmosMsg>> {
pub fn transfer_msgs<A: Into<String> + Clone>(
&self,
to: A,
) -> Result<Vec<CosmosMsg>, AssetError> {
self.0
.iter()
.map(|asset| asset.transfer_msg(to.clone()))
.collect::<StdResult<Vec<CosmosMsg>>>()
.collect()
}
}

Expand All @@ -447,7 +454,8 @@ impl AssetList {

#[cfg(test)]
mod test_helpers {
use super::{super::asset::Asset, *};
use super::*;
use crate::Asset;

pub fn uluna() -> AssetInfo {
AssetInfo::native("uluna")
Expand All @@ -473,7 +481,7 @@ mod test_helpers {
mod tests {
use cosmwasm_std::{
testing::MockApi, to_binary, BankMsg, Coin, CosmosMsg, Decimal, OverflowError,
OverflowOperation, Uint128, WasmMsg,
OverflowOperation, StdError, Uint128, WasmMsg,
};
use cw20::Cw20ExecuteMsg;

Expand Down Expand Up @@ -637,7 +645,12 @@ mod tests {
assert_eq!(asset_option, None);

let err = list.deduct(&Asset::new(uusd(), 57075u128));
assert_eq!(err, Err(StdError::generic_err("not found in asset list: native:uusd")));
assert_eq!(
err,
Err(AssetError::NotFoundInList {
info: "native:uusd".into(),
}),
);

list.deduct(&Asset::new(mock_token(), 12345u128)).unwrap();
let asset = list.find(&mock_token()).unwrap();
Expand All @@ -646,11 +659,12 @@ mod tests {
let err = list.deduct(&Asset::new(mock_token(), 99999u128));
assert_eq!(
err,
Err(StdError::overflow(OverflowError::new(
Err(OverflowError::new(
OverflowOperation::Sub,
Uint128::new(76543),
Uint128::new(99999)
)))
)
.into()),
);
}

Expand Down
22 changes: 20 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use cosmwasm_std::StdError;
use cosmwasm_std::{StdError, OverflowError};
use thiserror::Error;

#[derive(Error, Debug, PartialEq)]
pub enum AssetError {
#[error("{0}")]
#[error("std error encountered while handling assets: {0}")]
Std(#[from] StdError),

#[error("overflow error encountered while handling assets: {0}")]
Overflow(#[from] OverflowError),

#[error("invalid asset type `{ty}`; must be either `native` or `cw20`")]
InvalidAssetType {
ty: String,
Expand Down Expand Up @@ -40,4 +43,19 @@ pub enum AssetError {
denom: String,
whitelist: String,
},

#[error("asset `{info}` is not found in asset list")]
NotFoundInList {
info: String,
},

#[error("native coins do not have the `{method}` method")]
UnavailableMethodForNative {
method: String,
},

#[error("cannot cast asset {asset} to cosmwasm_std::Coin")]
CannotCastToStdCoin {
asset: String,
},
}
Loading

0 comments on commit 848ed65

Please sign in to comment.