nym_validator_client/nyxd/
coin.rs1use crate::nyxd::{Gas, GasPrice};
5pub use cosmrs::Coin as CosmosCoin;
6pub use cosmwasm_std::Coin as CosmWasmCoin;
7use cosmwasm_std::{Fraction, Uint128};
8use serde::{Deserialize, Serialize};
9use std::fmt;
10use std::ops::Div;
11use std::str::FromStr;
12use thiserror::Error;
13
14#[derive(Serialize, Deserialize, Clone, Copy, Default, Debug, PartialEq, Eq)]
15pub struct MismatchedDenoms;
16
17#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
22pub struct Coin {
23 pub amount: u128,
24 pub denom: String,
25}
26
27impl Div<GasPrice> for Coin {
28 type Output = Gas;
29
30 fn div(self, rhs: GasPrice) -> Self::Output {
31 &self / rhs
32 }
33}
34
35impl Div<GasPrice> for &Coin {
36 type Output = Gas;
37
38 fn div(self, rhs: GasPrice) -> Self::Output {
39 if self.denom != rhs.denom {
40 panic!(
41 "attempted to use two different denoms for gas calculation ({} and {})",
42 self.denom, rhs.denom
43 );
44 }
45
46 let Some(gas_price_inv) = rhs.amount.inv() else {
48 panic!("attempted to divide by zero!")
49 };
50
51 let implicit_gas_limit = Uint128::new(self.amount).mul_floor(gas_price_inv);
52 if implicit_gas_limit.u128() >= u64::MAX as u128 {
53 u64::MAX
54 } else {
55 implicit_gas_limit.u128() as u64
56 }
57 }
58}
59
60impl Coin {
61 pub fn new<S: Into<String>>(amount: u128, denom: S) -> Self {
62 Coin {
63 amount,
64 denom: denom.into(),
65 }
66 }
67
68 pub fn try_add(&self, other: &Self) -> Result<Self, MismatchedDenoms> {
69 if self.denom != other.denom {
70 Err(MismatchedDenoms)
71 } else {
72 Ok(Coin {
73 amount: self.amount + other.amount,
74 denom: self.denom.clone(),
75 })
76 }
77 }
78}
79
80impl fmt::Display for Coin {
81 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
82 write!(f, "{}{}", self.amount, self.denom)
83 }
84}
85
86impl From<Coin> for CosmosCoin {
87 fn from(coin: Coin) -> Self {
88 assert!(
89 coin.amount <= u64::MAX as u128,
90 "the coin amount is higher than the maximum supported by cosmrs"
91 );
92
93 CosmosCoin {
94 denom: coin
95 .denom
96 .parse()
97 .expect("the coin should have had a valid denom!"),
98 amount: (coin.amount as u64).into(),
99 }
100 }
101}
102
103impl From<CosmosCoin> for Coin {
104 fn from(coin: CosmosCoin) -> Self {
105 Coin {
106 amount: coin
107 .amount
108 .to_string()
109 .parse()
110 .expect("somehow failed to parse string representation of u64"),
111 denom: coin.denom.to_string(),
112 }
113 }
114}
115
116impl From<Coin> for CosmWasmCoin {
117 fn from(coin: Coin) -> Self {
118 CosmWasmCoin::new(coin.amount, coin.denom)
119 }
120}
121
122impl From<CosmWasmCoin> for Coin {
123 fn from(coin: CosmWasmCoin) -> Self {
124 Coin {
125 amount: coin.amount.u128(),
126 denom: coin.denom,
127 }
128 }
129}
130
131#[derive(Error, Debug, PartialEq, Eq)]
133pub enum CoinFromStrError {
134 #[error("Missing denominator")]
135 MissingDenom,
136 #[error("Missing amount or non-digit characters in amount")]
137 MissingAmount,
138 #[error("Invalid amount: {0}")]
139 InvalidAmount(#[from] std::num::ParseIntError),
140}
141
142impl FromStr for Coin {
143 type Err = CoinFromStrError;
144
145 fn from_str(s: &str) -> Result<Self, Self::Err> {
146 let pos = s
147 .find(|c: char| !c.is_ascii_digit())
148 .ok_or(CoinFromStrError::MissingDenom)?;
149 let (amount, denom) = s.split_at(pos);
150
151 if amount.is_empty() {
152 return Err(CoinFromStrError::MissingAmount);
153 }
154
155 Ok(Coin {
156 amount: amount.parse::<u128>()?,
157 denom: denom.to_string(),
158 })
159 }
160}
161
162pub trait CoinConverter {
163 type Target;
164
165 fn convert_coin(&self) -> Self::Target;
166}
167
168impl CoinConverter for CosmosCoin {
169 type Target = CosmWasmCoin;
170
171 fn convert_coin(&self) -> Self::Target {
172 CosmWasmCoin::new(self.amount, self.denom.to_string())
173 }
174}
175
176impl CoinConverter for CosmWasmCoin {
177 type Target = CosmosCoin;
178
179 fn convert_coin(&self) -> Self::Target {
180 assert!(
181 self.amount.u128() <= u64::MAX as u128,
182 "the coin amount is higher than the maximum supported by cosmrs"
183 );
184
185 CosmosCoin {
186 denom: self
187 .denom
188 .parse()
189 .expect("cosmwasm coin had an invalid amount assigned"),
190 amount: (self.amount.u128() as u64).into(),
191 }
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 #[should_panic]
201 fn division_by_zero_gas_price() {
202 let gas_price: GasPrice = "0unym".parse().unwrap();
203 let amount = Coin::new(123, "unym");
204 let _res = amount / gas_price;
205 }
206
207 #[test]
208 #[should_panic]
209 fn division_by_gas_price_of_different_denom() {
210 let gas_price: GasPrice = "0.025unyx".parse().unwrap();
211 let amount = Coin::new(123, "unym");
212 let _res = amount / gas_price;
213 }
214
215 #[test]
216 fn gas_price_division() {
217 let amount = Coin::new(3938, "unym");
218 let gas_price = "0.025unym".parse().unwrap();
219 let res = amount / gas_price;
220 assert_eq!(157520, res);
221
222 let amount = Coin::new(1234567890, "unym");
223 let gas_price = "0.025unym".parse().unwrap();
224 let res = amount / gas_price;
225 assert_eq!(49382715600, res);
226
227 let amount = Coin::new(1, "unym");
228 let gas_price = "0.025unym".parse().unwrap();
229 let res = amount / gas_price;
230 assert_eq!(40, res);
231
232 let amount = Coin::new(150_000_000, "unym");
233 let gas_price = "0.001234unym".parse().unwrap();
234 let res = amount / gas_price;
235 assert_eq!(121555915721, res);
236
237 let amount = Coin::new(150_000_000, "unym");
238 let gas_price = "1unym".parse().unwrap();
239 let res = amount / gas_price;
240 assert_eq!(150_000_000, res);
241
242 let amount = Coin::new(150_000_000, "unym");
243 let gas_price = "1234.56unym".parse().unwrap();
244 let res = amount / gas_price;
245 assert_eq!(121500, res);
246 }
247
248 #[test]
249 fn gas_price_division_identity() {
250 let amount = Coin::new(1234567890, "unym");
251 let gas_price: GasPrice = "0.025unym".parse().unwrap();
252 let res1 = (&amount) / gas_price.clone();
253 let res2 = &gas_price * res1;
254
255 assert_eq!(amount, Coin::from(res2));
256 }
257}