amount.rs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. use crate::Asset;
  2. use serde::{de, ser::SerializeStruct, Deserialize, Serialize, Serializer};
  3. /// The raw storage for cents, the more the better
  4. pub type AmountCents = i128;
  5. #[derive(Clone, Debug, Eq, PartialEq, thiserror::Error, Serialize)]
  6. pub enum Error {
  7. #[error("{0} is not a valid number")]
  8. NoANumber(String),
  9. #[error("Overflow")]
  10. Overflow,
  11. #[error("Invalid asset name: {0}")]
  12. InvalidAssetName(String),
  13. }
  14. #[derive(Debug, Serialize, Deserialize)]
  15. /// Human amount
  16. ///
  17. /// This amount is used to represent the amount in a human readable way. It is not being used
  18. /// internally but it is defined to be used by any external interface.
  19. pub struct HumanAmount {
  20. asset: Asset,
  21. amount: String,
  22. }
  23. #[derive(Debug, Serialize, Deserialize)]
  24. #[serde(untagged)]
  25. /// Any amount
  26. ///
  27. /// This amount will parse/encode any amount, either in cents or in human readable format.
  28. pub enum AnyAmount {
  29. /// Amount in cents
  30. Cent(Amount),
  31. /// Amount in human readable format
  32. Human(HumanAmount),
  33. }
  34. impl TryInto<Amount> for AnyAmount {
  35. type Error = Error;
  36. fn try_into(self) -> Result<Amount, Self::Error> {
  37. match self {
  38. Self::Cent(a) => Ok(a),
  39. Self::Human(h) => h.asset.from_human(&h.amount),
  40. }
  41. }
  42. }
  43. /// Amount
  44. ///
  45. /// The cents are stored in their lowest denomination, or their "cents". For
  46. /// instance a Bitcoin would be represented with a `precision` of 8, or dollars
  47. /// in a 2 or 3.
  48. ///
  49. /// For instance, if the dollar Asset has a `precision` of `2`, an `cents` of
  50. /// 1255 will represent `12.55 USD`.
  51. ///
  52. /// Amount will abstract all the math operations that can be performed with the
  53. /// amounts, guarding that the `Asset` are the same before performing any
  54. /// operation.
  55. ///
  56. ///
  57. /// The `cents` and `Asset` must be used to store amounts in the storage
  58. /// layer. Float or string representations should be used to display
  59. #[derive(
  60. Clone, Debug, Eq, PartialEq, Deserialize, borsh::BorshSerialize, borsh::BorshDeserialize,
  61. )]
  62. pub struct Amount {
  63. asset: Asset,
  64. #[serde(deserialize_with = "deserialize_string_to_amount")]
  65. cents: AmountCents,
  66. }
  67. impl Serialize for Amount {
  68. fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  69. where
  70. S: Serializer,
  71. {
  72. let mut state = serializer.serialize_struct("amount", 3)?;
  73. state.serialize_field("asset", &self.asset)?;
  74. state.serialize_field("cents", &self.cents.to_string())?;
  75. state.serialize_field("human", &self.to_string())?;
  76. state.end()
  77. }
  78. }
  79. fn deserialize_string_to_amount<'de, D>(deserializer: D) -> Result<AmountCents, D::Error>
  80. where
  81. D: de::Deserializer<'de>,
  82. {
  83. let s = String::deserialize(deserializer)?;
  84. s.parse::<AmountCents>().map_err(serde::de::Error::custom)
  85. }
  86. impl Amount {
  87. /// Creates a new amount from an asset and cents
  88. pub(crate) fn new(asset: Asset, cents: AmountCents) -> Self {
  89. Self { asset, cents }
  90. }
  91. #[inline]
  92. /// Returns the asset for this amount
  93. pub fn asset(&self) -> &Asset {
  94. &self.asset
  95. }
  96. #[inline]
  97. /// Return the cents
  98. pub fn cents(&self) -> AmountCents {
  99. self.cents
  100. }
  101. /// Attempts to do a checked addition of two amounts
  102. /// This will fails if assets are not the same or it overflows or underflows
  103. pub fn checked_add(&self, other: &Self) -> Option<Self> {
  104. if self.asset != other.asset {
  105. return None;
  106. }
  107. self.cents.checked_add(other.cents).map(|cents| Self {
  108. asset: self.asset.clone(),
  109. cents,
  110. })
  111. }
  112. }
  113. impl ToString for Amount {
  114. fn to_string(&self) -> String {
  115. self.try_into().unwrap()
  116. }
  117. }
  118. impl TryInto<String> for &Amount {
  119. type Error = Error;
  120. fn try_into(self) -> Result<String, Error> {
  121. let str = self.cents.abs().to_string();
  122. let precision = self.asset.precision;
  123. let len = u8::try_from(str.len()).map_err(|_| Error::Overflow)?;
  124. let (str, len) = if len < precision.checked_add(1).ok_or(Error::Overflow)? {
  125. (
  126. format!(
  127. "{}{}",
  128. "0".repeat(
  129. precision
  130. .checked_sub(len)
  131. .and_then(|x| x.checked_add(1))
  132. .ok_or(Error::Overflow)?
  133. .into()
  134. ),
  135. str
  136. ),
  137. precision.checked_add(1).ok_or(Error::Overflow)?,
  138. )
  139. } else {
  140. (str, len)
  141. };
  142. let (left, right) = str.split_at(len.checked_sub(precision).ok_or(Error::Overflow)?.into());
  143. Ok(format!(
  144. "{}{}.{}",
  145. if self.cents.is_negative() { "-" } else { "" },
  146. left,
  147. right
  148. )
  149. .trim_end_matches('0')
  150. .trim_end_matches('.')
  151. .to_owned())
  152. }
  153. }
  154. #[cfg(test)]
  155. mod test {
  156. use super::*;
  157. #[test]
  158. fn dollar() {
  159. let usd: Asset = "USD/4".parse().expect("asset");
  160. let amount = usd.new_amount(1022100);
  161. assert_eq!(amount.to_string(), "102.21");
  162. }
  163. #[test]
  164. fn bitcoin() {
  165. let btc: Asset = "BTC/8".parse().expect("asset");
  166. assert_eq!(btc.new_amount(1022100).to_string(), "0.010221");
  167. assert_eq!(btc.new_amount(10).to_string(), "0.0000001");
  168. assert_eq!(btc.new_amount(10000000).to_string(), "0.1");
  169. assert_eq!(btc.new_amount(100000000).to_string(), "1");
  170. assert_eq!(
  171. btc.new_amount(100000000)
  172. .checked_add(&btc.new_amount(100000000))
  173. .unwrap()
  174. .to_string(),
  175. "2",
  176. );
  177. assert_eq!(btc.new_amount(1000000000).to_string(), "10");
  178. }
  179. #[test]
  180. fn from_human() {
  181. let btc: Asset = "BTC/8".parse().expect("asset");
  182. let parsed_amount = btc.from_human("0.1").expect("valid amount");
  183. assert_eq!(parsed_amount.to_string(), "0.1");
  184. let parsed_amount = btc.from_human("-0.1").expect("valid amount");
  185. assert_eq!(parsed_amount.to_string(), "-0.1");
  186. let parsed_amount = btc.from_human("-0.000001").expect("valid amount");
  187. assert_eq!(parsed_amount.to_string(), "-0.000001");
  188. let parsed_amount = btc.from_human("-0.000000001").expect("valid amount");
  189. assert_eq!(parsed_amount.to_string(), "0");
  190. let parsed_amount = btc.from_human("0.000001").expect("valid amount");
  191. assert_eq!(parsed_amount.to_string(), "0.000001");
  192. let parsed_amount = btc.from_human("-0.000000100001").expect("valid amount");
  193. assert_eq!(parsed_amount.to_string(), "-0.0000001");
  194. let parsed_amount = btc.from_human("100000").expect("valid amount");
  195. assert_eq!(parsed_amount.to_string(), "100000");
  196. }
  197. }