| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 | 
							- use crate::Asset;
 
- use serde::{de, ser::SerializeStruct, Deserialize, Serialize, Serializer};
 
- /// The raw storage for cents, the more the better
 
- pub type AmountCents = i128;
 
- #[derive(Clone, Debug, Eq, PartialEq, thiserror::Error, Serialize)]
 
- pub enum Error {
 
-     #[error("{0} is not a valid number")]
 
-     NoANumber(String),
 
-     #[error("Overflow")]
 
-     Overflow,
 
-     #[error("Invalid asset name: {0}")]
 
-     InvalidAssetName(String),
 
- }
 
- #[derive(Debug, Serialize, Deserialize)]
 
- /// Human amount
 
- ///
 
- /// This amount is used to represent the amount in a human readable way.  It is not being used
 
- /// internally but it is defined to be used by any external interface.
 
- pub struct HumanAmount {
 
-     asset: Asset,
 
-     amount: String,
 
- }
 
- #[derive(Debug, Serialize, Deserialize)]
 
- #[serde(untagged)]
 
- /// Any amount
 
- ///
 
- /// This amount will parse/encode any amount, either in cents or in human readable format.
 
- pub enum AnyAmount {
 
-     /// Amount in cents
 
-     Cent(Amount),
 
-     /// Amount in human readable format
 
-     Human(HumanAmount),
 
- }
 
- impl TryInto<Amount> for AnyAmount {
 
-     type Error = Error;
 
-     fn try_into(self) -> Result<Amount, Self::Error> {
 
-         match self {
 
-             Self::Cent(a) => Ok(a),
 
-             Self::Human(h) => h.asset.from_human(&h.amount),
 
-         }
 
-     }
 
- }
 
- /// Amount
 
- ///
 
- /// The cents are stored in their lowest denomination, or their "cents".  For
 
- /// instance a Bitcoin would be represented with a `precision` of 8, or dollars
 
- /// in a 2 or 3.
 
- ///
 
- /// For instance, if the dollar Asset has a `precision` of `2`, an `cents` of
 
- /// 1255 will represent `12.55 USD`.
 
- ///
 
- /// Amount will abstract all the math operations that can be performed with the
 
- /// amounts, guarding that the `Asset` are the same before performing any
 
- /// operation.
 
- ///
 
- ///
 
- /// The `cents` and `Asset` must be used to store amounts in the storage
 
- /// layer. Float or string representations should be used to display
 
- #[derive(
 
-     Clone, Debug, Eq, PartialEq, Deserialize, borsh::BorshSerialize, borsh::BorshDeserialize,
 
- )]
 
- pub struct Amount {
 
-     asset: Asset,
 
-     #[serde(deserialize_with = "deserialize_string_to_amount")]
 
-     cents: AmountCents,
 
- }
 
- impl Serialize for Amount {
 
-     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
 
-     where
 
-         S: Serializer,
 
-     {
 
-         let mut state = serializer.serialize_struct("amount", 3)?;
 
-         state.serialize_field("asset", &self.asset)?;
 
-         state.serialize_field("cents", &self.cents.to_string())?;
 
-         state.serialize_field("human", &self.to_string())?;
 
-         state.end()
 
-     }
 
- }
 
- fn deserialize_string_to_amount<'de, D>(deserializer: D) -> Result<AmountCents, D::Error>
 
- where
 
-     D: de::Deserializer<'de>,
 
- {
 
-     let s = String::deserialize(deserializer)?;
 
-     s.parse::<AmountCents>().map_err(serde::de::Error::custom)
 
- }
 
- impl Amount {
 
-     /// Creates a new amount from an asset and cents
 
-     pub(crate) fn new(asset: Asset, cents: AmountCents) -> Self {
 
-         Self { asset, cents }
 
-     }
 
-     #[inline]
 
-     /// Returns the asset for this amount
 
-     pub fn asset(&self) -> &Asset {
 
-         &self.asset
 
-     }
 
-     #[inline]
 
-     /// Return the cents
 
-     pub fn cents(&self) -> AmountCents {
 
-         self.cents
 
-     }
 
-     /// Attempts to do a checked addition of two amounts
 
-     /// This will fails if assets are not the same or it overflows or underflows
 
-     pub fn checked_add(&self, other: &Self) -> Option<Self> {
 
-         if self.asset != other.asset {
 
-             return None;
 
-         }
 
-         self.cents.checked_add(other.cents).map(|cents| Self {
 
-             asset: self.asset.clone(),
 
-             cents,
 
-         })
 
-     }
 
- }
 
- impl ToString for Amount {
 
-     fn to_string(&self) -> String {
 
-         self.try_into().unwrap()
 
-     }
 
- }
 
- impl TryInto<String> for &Amount {
 
-     type Error = Error;
 
-     fn try_into(self) -> Result<String, Error> {
 
-         let str = self.cents.abs().to_string();
 
-         let precision = self.asset.precision;
 
-         let len = u8::try_from(str.len()).map_err(|_| Error::Overflow)?;
 
-         let (str, len) = if len < precision.checked_add(1).ok_or(Error::Overflow)? {
 
-             (
 
-                 format!(
 
-                     "{}{}",
 
-                     "0".repeat(
 
-                         precision
 
-                             .checked_sub(len)
 
-                             .and_then(|x| x.checked_add(1))
 
-                             .ok_or(Error::Overflow)?
 
-                             .into()
 
-                     ),
 
-                     str
 
-                 ),
 
-                 precision.checked_add(1).ok_or(Error::Overflow)?,
 
-             )
 
-         } else {
 
-             (str, len)
 
-         };
 
-         let (left, right) = str.split_at(len.checked_sub(precision).ok_or(Error::Overflow)?.into());
 
-         Ok(format!(
 
-             "{}{}.{}",
 
-             if self.cents.is_negative() { "-" } else { "" },
 
-             left,
 
-             right
 
-         )
 
-         .trim_end_matches('0')
 
-         .trim_end_matches('.')
 
-         .to_owned())
 
-     }
 
- }
 
- #[cfg(test)]
 
- mod test {
 
-     use super::*;
 
-     #[test]
 
-     fn dollar() {
 
-         let usd: Asset = "USD/4".parse().expect("asset");
 
-         let amount = usd.new_amount(1022100);
 
-         assert_eq!(amount.to_string(), "102.21");
 
-     }
 
-     #[test]
 
-     fn bitcoin() {
 
-         let btc: Asset = "BTC/8".parse().expect("asset");
 
-         assert_eq!(btc.new_amount(1022100).to_string(), "0.010221");
 
-         assert_eq!(btc.new_amount(10).to_string(), "0.0000001");
 
-         assert_eq!(btc.new_amount(10000000).to_string(), "0.1");
 
-         assert_eq!(btc.new_amount(100000000).to_string(), "1");
 
-         assert_eq!(
 
-             btc.new_amount(100000000)
 
-                 .checked_add(&btc.new_amount(100000000))
 
-                 .unwrap()
 
-                 .to_string(),
 
-             "2",
 
-         );
 
-         assert_eq!(btc.new_amount(1000000000).to_string(), "10");
 
-     }
 
-     #[test]
 
-     fn from_human() {
 
-         let btc: Asset = "BTC/8".parse().expect("asset");
 
-         let parsed_amount = btc.from_human("0.1").expect("valid amount");
 
-         assert_eq!(parsed_amount.to_string(), "0.1");
 
-         let parsed_amount = btc.from_human("-0.1").expect("valid amount");
 
-         assert_eq!(parsed_amount.to_string(), "-0.1");
 
-         let parsed_amount = btc.from_human("-0.000001").expect("valid amount");
 
-         assert_eq!(parsed_amount.to_string(), "-0.000001");
 
-         let parsed_amount = btc.from_human("-0.000000001").expect("valid amount");
 
-         assert_eq!(parsed_amount.to_string(), "0");
 
-         let parsed_amount = btc.from_human("0.000001").expect("valid amount");
 
-         assert_eq!(parsed_amount.to_string(), "0.000001");
 
-         let parsed_amount = btc.from_human("-0.000000100001").expect("valid amount");
 
-         assert_eq!(parsed_amount.to_string(), "-0.0000001");
 
-         let parsed_amount = btc.from_human("100000").expect("valid amount");
 
-         assert_eq!(parsed_amount.to_string(), "100000");
 
-     }
 
- }
 
 
  |