Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Construct Coin from String #1684

Merged
merged 6 commits into from
May 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ and this project adheres to
- cosmwasm-vm: Add `Cache::save_wasm_unchecked` to save Wasm blobs that have
been checked before. This is useful for state-sync where we know the Wasm code
was checked when it was first uploaded. ([#1635])
- cosmwasm-std: Add `FromStr` impl for `Coin`. ([#1684])

[#1635]: https://github.com/CosmWasm/cosmwasm/pull/1635
[#1684]: https://github.com/CosmWasm/cosmwasm/pull/1684

### Changed

Expand Down
73 changes: 71 additions & 2 deletions packages/std/src/coin.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::{fmt, str::FromStr};

use crate::math::Uint128;
use crate::{errors::CoinFromStrError, math::Uint128};

#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, JsonSchema)]
pub struct Coin {
Expand All @@ -19,6 +19,26 @@ impl Coin {
}
}

impl FromStr for Coin {
type Err = CoinFromStrError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let pos = s
.find(|c: char| !c.is_ascii_digit())
.ok_or(CoinFromStrError::MissingDenom)?;
let (amount, denom) = s.split_at(pos);

if amount.is_empty() {
return Err(CoinFromStrError::MissingAmount);
}

Ok(Coin {
amount: amount.parse::<u128>()?.into(),
denom: denom.to_string(),
})
}
}

impl fmt::Display for Coin {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// We use the formatting without a space between amount and denom,
Expand Down Expand Up @@ -166,4 +186,53 @@ mod tests {
// less than same type
assert!(has_coins(&wallet, &coin(777, "ETH")));
}

#[test]
fn parse_coin() {
let expected = Coin::new(123, "ucosm");
assert_eq!("123ucosm".parse::<Coin>().unwrap(), expected);
// leading zeroes should be ignored
assert_eq!("00123ucosm".parse::<Coin>().unwrap(), expected);
// 0 amount parses correctly
assert_eq!("0ucosm".parse::<Coin>().unwrap(), Coin::new(0, "ucosm"));
// ibc denom should work
let ibc_str = "11111ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2";
let ibc_coin = Coin::new(
11111,
"ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
);
assert_eq!(ibc_str.parse::<Coin>().unwrap(), ibc_coin);

// error cases
assert_eq!(
Coin::from_str("123").unwrap_err(),
CoinFromStrError::MissingDenom
);
assert_eq!(
Coin::from_str("ucosm").unwrap_err(), // no amount
CoinFromStrError::MissingAmount
);
assert_eq!(
Coin::from_str("-123ucosm").unwrap_err(), // negative amount
CoinFromStrError::MissingAmount
);
assert_eq!(
Coin::from_str("").unwrap_err(), // empty input
CoinFromStrError::MissingDenom
);
assert_eq!(
Coin::from_str(" 1ucosm").unwrap_err(), // unsupported whitespace
CoinFromStrError::MissingAmount
);
assert_eq!(
Coin::from_str("�1ucosm").unwrap_err(), // other broken data
CoinFromStrError::MissingAmount
);
assert_eq!(
Coin::from_str("340282366920938463463374607431768211456ucosm")
.unwrap_err()
.to_string(),
"Invalid amount: number too large to fit in target type"
);
webmaster128 marked this conversation as resolved.
Show resolved Hide resolved
}
}
2 changes: 1 addition & 1 deletion packages/std/src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ mod verification_error;
pub use recover_pubkey_error::RecoverPubkeyError;
pub use std_error::{
CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError,
ConversionOverflowError, DivideByZeroError, OverflowError, OverflowOperation,
CoinFromStrError, ConversionOverflowError, DivideByZeroError, OverflowError, OverflowOperation,
RoundUpOverflowError, StdError, StdResult,
};
pub use system_error::SystemError;
Expand Down
22 changes: 22 additions & 0 deletions packages/std/src/errors/std_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,28 @@ pub enum CheckedFromRatioError {
#[error("Round up operation failed because of overflow")]
pub struct RoundUpOverflowError;

#[derive(Error, Debug, PartialEq, Eq)]
pub enum CoinFromStrError {
#[error("Missing denominator")]
MissingDenom,
#[error("Missing amount or non-digit characters in amount")]
MissingAmount,
#[error("Invalid amount: {0}")]
InvalidAmount(std::num::ParseIntError),
}

impl From<std::num::ParseIntError> for CoinFromStrError {
fn from(value: std::num::ParseIntError) -> Self {
Self::InvalidAmount(value)
}
}

impl From<CoinFromStrError> for StdError {
fn from(value: CoinFromStrError) -> Self {
Self::generic_err(format!("Parsing Coin: {}", value))
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down