1use cosmwasm_schema::cw_serde;
5use cosmwasm_std::OverflowError;
6use cosmwasm_std::Uint128;
7use cosmwasm_std::{Decimal, Fraction};
8use serde::de::Error;
9use serde::{Deserialize, Deserializer};
10use std::fmt::{self, Display, Formatter};
11use std::ops::{Deref, Mul};
12use std::str::FromStr;
13use thiserror::Error;
14
15pub type IdentityKey = String;
17pub type IdentityKeyRef<'a> = &'a str;
18
19pub fn truncate_decimal(amount: Decimal) -> Uint128 {
20 Uint128::new(1).mul_floor(amount)
21}
22
23#[derive(Error, Debug)]
24pub enum ContractsCommonError {
25 #[error("Provided percent value ({0}) is greater than 100%")]
26 InvalidPercent(String),
27
28 #[error("{source}")]
29 StdErr {
30 #[from]
31 source: cosmwasm_std::StdError,
32 },
33}
34
35#[cw_serde]
38#[derive(Copy, Default, PartialOrd, Ord, Eq)]
39pub struct Percent(#[serde(deserialize_with = "de_decimal_percent")] Decimal);
40
41impl Percent {
42 pub fn new(value: Decimal) -> Result<Self, ContractsCommonError> {
43 if value > Decimal::one() {
44 Err(ContractsCommonError::InvalidPercent(value.to_string()))
45 } else {
46 Ok(Percent(value))
47 }
48 }
49
50 pub fn is_zero(&self) -> bool {
51 self.0 == Decimal::zero()
52 }
53
54 pub fn is_hundred(&self) -> bool {
55 self == &Self::hundred()
56 }
57
58 pub const fn zero() -> Self {
59 Self(Decimal::zero())
60 }
61
62 pub const fn hundred() -> Self {
63 Self(Decimal::one())
64 }
65
66 pub fn from_percentage_value(value: u64) -> Result<Self, ContractsCommonError> {
67 Percent::new(Decimal::percent(value))
68 }
69
70 pub fn value(&self) -> Decimal {
71 self.0
72 }
73
74 pub fn round_to_integer(&self) -> u8 {
75 let hundred = Decimal::from_ratio(100u32, 1u32);
76 truncate_decimal(hundred * self.0).u128() as u8
78 }
79
80 pub fn checked_pow(&self, exp: u32) -> Result<Self, OverflowError> {
81 self.0.checked_pow(exp).map(Percent)
82 }
83
84 #[must_use = "this returns the result of the operation, without modifying the original"]
89 pub fn round_to_two_decimal_places(&self) -> Self {
90 let raw = self.0;
91
92 const DECIMAL_FRACTIONAL: Uint128 = Uint128::new(1_000_000_000_000_000_000u128); const THRESHOLD: Decimal = Decimal::permille(5); debug_assert_eq!(
97 DECIMAL_FRACTIONAL,
98 Uint128::new(10).pow(Decimal::DECIMAL_PLACES)
99 );
100
101 let int = (raw.atomics() * Uint128::new(100)) / DECIMAL_FRACTIONAL;
102
103 #[allow(clippy::unwrap_used)]
104 let floored = Decimal::from_atomics(int, 2).unwrap();
105 let diff = raw - floored;
106 let rounded = if diff >= THRESHOLD {
107 floored + Decimal::percent(1)
109 } else {
110 floored
111 };
112 Percent(rounded)
113 }
114
115 #[must_use = "this returns the result of the operation, without modifying the original"]
116 pub fn average(&self, other: &Self) -> Self {
117 let sum = self.0 + other.0;
118 let inner = Decimal::from_ratio(sum.numerator(), sum.denominator() * Uint128::new(2));
119 Percent(inner)
120 }
121}
122
123impl Display for Percent {
124 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
125 let adjusted = Decimal::from_ratio(100u32, 1u32) * self.0;
126 write!(f, "{adjusted}%")
127 }
128}
129
130impl FromStr for Percent {
131 type Err = ContractsCommonError;
132
133 fn from_str(s: &str) -> Result<Self, Self::Err> {
134 Percent::new(Decimal::from_str(s)?)
135 }
136}
137
138impl Mul<Decimal> for Percent {
139 type Output = Decimal;
140
141 fn mul(self, rhs: Decimal) -> Self::Output {
142 self.0 * rhs
143 }
144}
145
146impl Mul<Percent> for Decimal {
147 type Output = Decimal;
148
149 fn mul(self, rhs: Percent) -> Self::Output {
150 rhs * self
151 }
152}
153
154impl Fraction<Uint128> for Percent {
155 fn numerator(&self) -> Uint128 {
156 self.0.numerator()
157 }
158
159 fn denominator(&self) -> Uint128 {
160 self.0.denominator()
161 }
162
163 fn inv(&self) -> Option<Self> {
164 Percent::new(self.0.inv()?).ok()
165 }
166}
167
168impl Deref for Percent {
169 type Target = Decimal;
170
171 fn deref(&self) -> &Self::Target {
172 &self.0
173 }
174}
175
176#[cfg(feature = "naive_float")]
178pub trait NaiveFloat {
179 fn naive_to_f64(&self) -> f64;
180
181 fn naive_try_from_f64(val: f64) -> Result<Self, ContractsCommonError>
182 where
183 Self: Sized;
184}
185
186#[cfg(feature = "naive_float")]
187impl NaiveFloat for Decimal {
188 fn naive_to_f64(&self) -> f64 {
189 use cosmwasm_std::Fraction;
190
191 self.numerator().u128() as f64 / self.denominator().u128() as f64
194 }
195
196 fn naive_try_from_f64(val: f64) -> Result<Self, ContractsCommonError>
197 where
198 Self: Sized,
199 {
200 if !(0. ..=1.).contains(&val) {
202 return Err(ContractsCommonError::InvalidPercent(val.to_string()));
203 }
204
205 fn gcd(mut x: u64, mut y: u64) -> u64 {
206 while y > 0 {
207 let rem = x % y;
208 x = y;
209 y = rem;
210 }
211
212 x
213 }
214
215 fn to_rational(x: f64) -> (u64, u64) {
216 let log = x.log2().floor();
217 if log >= 0.0 {
218 (x as u64, 1)
219 } else {
220 let num: u64 = (x / f64::EPSILON) as _;
221 let den: u64 = (1.0 / f64::EPSILON) as _;
222 let gcd = gcd(num, den);
223 (num / gcd, den / gcd)
224 }
225 }
226
227 let (n, d) = to_rational(val);
228 Ok(Decimal::from_ratio(n, d))
229 }
230}
231
232#[cfg(feature = "naive_float")]
233impl NaiveFloat for Percent {
234 fn naive_to_f64(&self) -> f64 {
235 self.0.naive_to_f64()
236 }
237
238 fn naive_try_from_f64(val: f64) -> Result<Self, ContractsCommonError>
239 where
240 Self: Sized,
241 {
242 Percent::new(Decimal::naive_try_from_f64(val)?)
243 }
244}
245
246fn de_decimal_percent<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
248where
249 D: Deserializer<'de>,
250{
251 let v = Decimal::deserialize(deserializer)?;
252 if v > Decimal::one() {
253 Err(D::Error::custom(
254 "provided decimal percent is larger than 100%",
255 ))
256 } else {
257 Ok(v)
258 }
259}
260
261fn default_unknown() -> String {
262 "unknown".to_string()
263}
264
265#[cw_serde]
268#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
269pub struct ContractBuildInformation {
270 #[serde(default = "default_unknown")]
272 pub contract_name: String,
273
274 pub build_timestamp: String,
277
278 pub build_version: String,
281
282 pub commit_sha: String,
285
286 pub commit_timestamp: String,
289
290 pub commit_branch: String,
293
294 pub rustc_version: String,
297
298 #[serde(default = "default_unknown")]
301 pub cargo_debug: String,
302
303 #[serde(default = "default_unknown")]
306 pub cargo_opt_level: String,
307}
308
309impl ContractBuildInformation {
310 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
311 ContractBuildInformation {
312 contract_name: name.into(),
313 build_version: version.into(),
314 build_timestamp: env!("VERGEN_BUILD_TIMESTAMP").to_string(),
315 commit_sha: option_env!("VERGEN_GIT_SHA")
316 .unwrap_or("UNKNOWN")
317 .to_string(),
318 commit_timestamp: option_env!("VERGEN_GIT_COMMIT_TIMESTAMP")
319 .unwrap_or("UNKNOWN")
320 .to_string(),
321 commit_branch: option_env!("VERGEN_GIT_BRANCH")
322 .unwrap_or("UNKNOWN")
323 .to_string(),
324 rustc_version: env!("VERGEN_RUSTC_SEMVER").to_string(),
325 cargo_debug: env!("VERGEN_CARGO_DEBUG").to_string(),
326 cargo_opt_level: env!("VERGEN_CARGO_OPT_LEVEL").to_string(),
327 }
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn percent_serde() {
337 let valid_value = Percent::from_percentage_value(80).unwrap();
338 let serialized = serde_json::to_string(&valid_value).unwrap();
339
340 let deserialized: Percent = serde_json::from_str(&serialized).unwrap();
341 assert_eq!(valid_value, deserialized);
342
343 let invalid_values = vec!["\"42\"", "\"1.1\"", "\"1.00000001\"", "\"foomp\"", "\"1a\""];
344 for invalid_value in invalid_values {
345 assert!(serde_json::from_str::<'_, Percent>(invalid_value).is_err())
346 }
347 assert_eq!(
348 serde_json::from_str::<'_, Percent>("\"0.95\"").unwrap(),
349 Percent::from_percentage_value(95).unwrap()
350 )
351 }
352
353 #[test]
354 fn percent_to_absolute_integer() {
355 let p = serde_json::from_str::<'_, Percent>("\"0.0001\"").unwrap();
356 assert_eq!(p.round_to_integer(), 0);
357
358 let p = serde_json::from_str::<'_, Percent>("\"0.0099\"").unwrap();
359 assert_eq!(p.round_to_integer(), 0);
360
361 let p = serde_json::from_str::<'_, Percent>("\"0.0199\"").unwrap();
362 assert_eq!(p.round_to_integer(), 1);
363
364 let p = serde_json::from_str::<'_, Percent>("\"0.45123\"").unwrap();
365 assert_eq!(p.round_to_integer(), 45);
366
367 let p = serde_json::from_str::<'_, Percent>("\"0.999999999\"").unwrap();
368 assert_eq!(p.round_to_integer(), 99);
369
370 let p = serde_json::from_str::<'_, Percent>("\"1.00\"").unwrap();
371 assert_eq!(p.round_to_integer(), 100);
372 }
373
374 #[test]
375 #[cfg(feature = "naive_float")]
376 fn naive_float_conversion() {
377 let float: f64 = "0.546295475423853".parse().unwrap();
380 let percent: Percent = "0.546295475423853".parse().unwrap();
381
382 assert_eq!(float, percent.naive_to_f64());
383
384 let epsilon = Decimal::from_ratio(1u64, 1000000000000000u64);
385 let converted = Percent::naive_try_from_f64(float).unwrap();
386
387 assert!(converted.0 - converted.0 < epsilon);
388 }
389
390 #[test]
391 fn rounding_percent() {
392 let test_cases = vec![
393 ("0", "0"),
394 ("0.1", "0.1"),
395 ("0.12", "0.12"),
396 ("0.12", "0.123"),
397 ("0.12", "0.123456789"),
398 ("0.13", "0.125"),
399 ("0.13", "0.126"),
400 ("0.13", "0.126436545676"),
401 ("0.99", "0.99"),
402 ("0.99", "0.994"),
403 ("1", "0.999"),
404 ("1", "0.995"),
405 ];
406 for (expected, input) in test_cases {
407 let expected: Percent = expected.parse().unwrap();
408 let pre_truncated: Percent = input.parse().unwrap();
409 assert_eq!(expected, pre_truncated.round_to_two_decimal_places())
410 }
411 }
412
413 #[test]
414 fn calculating_average() -> anyhow::Result<()> {
415 fn p(raw: &str) -> Percent {
416 raw.parse().unwrap()
417 }
418
419 assert_eq!(p("0.1").average(&p("0.1")), p("0.1"));
420 assert_eq!(p("0.1").average(&p("0.2")), p("0.15"));
421 assert_eq!(p("1").average(&p("0")), p("0.5"));
422 assert_eq!(p("0.123").average(&p("0.456")), p("0.2895"));
423
424 Ok(())
425 }
426}