1use std::fmt::{self, Display};
4use std::ops::{Add, Sub};
5use std::str::FromStr;
6
7use borsh::{BorshDeserialize, BorshSerialize};
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9
10use crate::error::{ParseAmountError, ParseGasError};
11
12const YOCTO_PER_NEAR: u128 = 1_000_000_000_000_000_000_000_000;
14const YOCTO_PER_MILLINEAR: u128 = 1_000_000_000_000_000_000_000;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
52pub struct NearToken(u128);
53
54impl NearToken {
55 pub const ZERO: Self = Self(0);
57 pub const ONE_YOCTO: Self = Self(1);
59 pub const ONE_MILLINEAR: Self = Self(YOCTO_PER_MILLINEAR);
61 pub const ONE_NEAR: Self = Self(YOCTO_PER_NEAR);
63
64 pub const fn near(near: u128) -> Self {
79 Self(near * YOCTO_PER_NEAR)
80 }
81
82 pub const fn millinear(millinear: u128) -> Self {
93 Self(millinear * YOCTO_PER_MILLINEAR)
94 }
95
96 pub const fn yocto(yocto: u128) -> Self {
107 Self(yocto)
108 }
109
110 pub const fn from_yoctonear(yocto: u128) -> Self {
116 Self(yocto)
117 }
118
119 pub const fn from_millinear(millinear: u128) -> Self {
121 Self(millinear * YOCTO_PER_MILLINEAR)
122 }
123
124 pub const fn from_near(near: u128) -> Self {
126 Self(near * YOCTO_PER_NEAR)
127 }
128
129 pub fn from_near_decimal(s: &str) -> Result<Self, ParseAmountError> {
131 let s = s.trim();
132
133 if let Some(dot_pos) = s.find('.') {
134 let integer_part = &s[..dot_pos];
136 let decimal_part = &s[dot_pos + 1..];
137
138 let integer: u128 = if integer_part.is_empty() {
140 0
141 } else {
142 integer_part
143 .parse()
144 .map_err(|_| ParseAmountError::InvalidNumber(s.to_string()))?
145 };
146
147 let decimal_str = if decimal_part.len() > 24 {
149 &decimal_part[..24]
150 } else {
151 decimal_part
152 };
153
154 let decimal: u128 = if decimal_str.is_empty() {
155 0
156 } else {
157 decimal_str
158 .parse()
159 .map_err(|_| ParseAmountError::InvalidNumber(s.to_string()))?
160 };
161
162 let decimal_scale = 24 - decimal_str.len();
164 let decimal_yocto = decimal * 10u128.pow(decimal_scale as u32);
165
166 let total = integer
167 .checked_mul(YOCTO_PER_NEAR)
168 .and_then(|v| v.checked_add(decimal_yocto))
169 .ok_or(ParseAmountError::Overflow)?;
170
171 Ok(Self(total))
172 } else {
173 let near: u128 = s
175 .parse()
176 .map_err(|_| ParseAmountError::InvalidNumber(s.to_string()))?;
177 near.checked_mul(YOCTO_PER_NEAR)
178 .map(Self)
179 .ok_or(ParseAmountError::Overflow)
180 }
181 }
182
183 pub const fn as_yoctonear(&self) -> u128 {
185 self.0
186 }
187
188 pub fn as_near_f64(&self) -> f64 {
190 self.0 as f64 / YOCTO_PER_NEAR as f64
191 }
192
193 pub const fn as_near(&self) -> u128 {
195 self.0 / YOCTO_PER_NEAR
196 }
197
198 pub fn checked_add(self, other: Self) -> Option<Self> {
200 self.0.checked_add(other.0).map(Self)
201 }
202
203 pub fn checked_sub(self, other: Self) -> Option<Self> {
205 self.0.checked_sub(other.0).map(Self)
206 }
207
208 pub fn saturating_add(self, other: Self) -> Self {
210 Self(self.0.saturating_add(other.0))
211 }
212
213 pub fn saturating_sub(self, other: Self) -> Self {
215 Self(self.0.saturating_sub(other.0))
216 }
217
218 pub const fn is_zero(&self) -> bool {
220 self.0 == 0
221 }
222}
223
224impl FromStr for NearToken {
225 type Err = ParseAmountError;
226
227 fn from_str(s: &str) -> Result<Self, Self::Err> {
228 let s = s.trim();
229
230 if let Some(value) = s.strip_suffix(" NEAR").or_else(|| s.strip_suffix(" near")) {
232 return Self::from_near_decimal(value.trim());
233 }
234
235 if let Some(value) = s
237 .strip_suffix(" milliNEAR")
238 .or_else(|| s.strip_suffix(" mNEAR"))
239 {
240 let v: u128 = value
241 .trim()
242 .parse()
243 .map_err(|_| ParseAmountError::InvalidNumber(s.to_string()))?;
244 return v
245 .checked_mul(YOCTO_PER_MILLINEAR)
246 .map(Self)
247 .ok_or(ParseAmountError::Overflow);
248 }
249
250 if let Some(value) = s
252 .strip_suffix(" yoctoNEAR")
253 .or_else(|| s.strip_suffix(" yocto"))
254 {
255 let v: u128 = value
256 .trim()
257 .parse()
258 .map_err(|_| ParseAmountError::InvalidNumber(s.to_string()))?;
259 return Ok(Self(v));
260 }
261
262 if s.chars().all(|c| c.is_ascii_digit() || c == '.') {
264 return Err(ParseAmountError::AmbiguousAmount(s.to_string()));
265 }
266
267 Err(ParseAmountError::InvalidFormat(s.to_string()))
268 }
269}
270
271impl TryFrom<&str> for NearToken {
272 type Error = ParseAmountError;
273
274 fn try_from(s: &str) -> Result<Self, Self::Error> {
275 s.parse()
276 }
277}
278
279impl Display for NearToken {
280 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281 if self.0 == 0 {
282 return write!(f, "0 NEAR");
283 }
284
285 let near = self.0 / YOCTO_PER_NEAR;
286 let remainder = self.0 % YOCTO_PER_NEAR;
287
288 if remainder == 0 {
289 write!(f, "{} NEAR", near)
290 } else {
291 let decimal = format!("{:024}", remainder);
293 let decimal = decimal.trim_end_matches('0');
294 let decimal_len = decimal.len().min(5);
295 write!(f, "{}.{} NEAR", near, &decimal[..decimal_len])
296 }
297 }
298}
299
300impl Add for NearToken {
301 type Output = Self;
302
303 fn add(self, other: Self) -> Self {
304 Self(self.0 + other.0)
305 }
306}
307
308impl Sub for NearToken {
309 type Output = Self;
310
311 fn sub(self, other: Self) -> Self {
312 Self(self.0 - other.0)
313 }
314}
315
316impl Serialize for NearToken {
318 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
319 s.serialize_str(&self.0.to_string())
320 }
321}
322
323impl<'de> Deserialize<'de> for NearToken {
324 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
325 let s: String = serde::Deserialize::deserialize(d)?;
326 Ok(Self(s.parse().map_err(serde::de::Error::custom)?))
327 }
328}
329
330impl BorshSerialize for NearToken {
331 fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
332 borsh::BorshSerialize::serialize(&self.0, writer)
333 }
334}
335
336impl BorshDeserialize for NearToken {
337 fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
338 Ok(Self(u128::deserialize_reader(reader)?))
339 }
340}
341
342const GAS_PER_PGAS: u64 = 1_000_000_000_000_000;
348const GAS_PER_TGAS: u64 = 1_000_000_000_000;
350const GAS_PER_GGAS: u64 = 1_000_000_000;
352
353#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
386pub struct Gas(u64);
387
388impl Gas {
389 pub const ZERO: Self = Self(0);
391 pub const ONE_GGAS: Self = Self(GAS_PER_GGAS);
393 pub const ONE_TGAS: Self = Self(GAS_PER_TGAS);
395 pub const ONE_PGAS: Self = Self(GAS_PER_PGAS);
397
398 pub const DEFAULT: Self = Self::from_tgas(30);
400
401 pub const MAX: Self = Self::from_tgas(1_000);
403
404 pub const fn tgas(tgas: u64) -> Self {
419 Self(tgas * GAS_PER_TGAS)
420 }
421
422 pub const fn ggas(ggas: u64) -> Self {
433 Self(ggas * GAS_PER_GGAS)
434 }
435
436 pub const fn from_gas(gas: u64) -> Self {
442 Self(gas)
443 }
444
445 pub const fn from_ggas(ggas: u64) -> Self {
447 Self(ggas * GAS_PER_GGAS)
448 }
449
450 pub const fn from_tgas(tgas: u64) -> Self {
452 Self(tgas * GAS_PER_TGAS)
453 }
454
455 pub const fn as_gas(&self) -> u64 {
457 self.0
458 }
459
460 pub const fn as_tgas(&self) -> u64 {
462 self.0 / GAS_PER_TGAS
463 }
464
465 pub const fn as_ggas(&self) -> u64 {
467 self.0 / GAS_PER_GGAS
468 }
469
470 pub fn checked_add(self, other: Self) -> Option<Self> {
472 self.0.checked_add(other.0).map(Self)
473 }
474
475 pub fn checked_sub(self, other: Self) -> Option<Self> {
477 self.0.checked_sub(other.0).map(Self)
478 }
479
480 pub const fn is_zero(&self) -> bool {
482 self.0 == 0
483 }
484}
485
486impl FromStr for Gas {
487 type Err = ParseGasError;
488
489 fn from_str(s: &str) -> Result<Self, Self::Err> {
490 let s = s.trim();
491
492 if let Some(value) = s
494 .strip_suffix(" Tgas")
495 .or_else(|| s.strip_suffix(" tgas"))
496 .or_else(|| s.strip_suffix(" TGas"))
497 {
498 let v: u64 = value
499 .trim()
500 .parse()
501 .map_err(|_| ParseGasError::InvalidNumber(s.to_string()))?;
502 return v
503 .checked_mul(GAS_PER_TGAS)
504 .map(Self)
505 .ok_or(ParseGasError::Overflow);
506 }
507
508 if let Some(value) = s
510 .strip_suffix(" Ggas")
511 .or_else(|| s.strip_suffix(" ggas"))
512 .or_else(|| s.strip_suffix(" GGas"))
513 {
514 let v: u64 = value
515 .trim()
516 .parse()
517 .map_err(|_| ParseGasError::InvalidNumber(s.to_string()))?;
518 return v
519 .checked_mul(GAS_PER_GGAS)
520 .map(Self)
521 .ok_or(ParseGasError::Overflow);
522 }
523
524 if let Some(value) = s.strip_suffix(" gas") {
526 let v: u64 = value
527 .trim()
528 .parse()
529 .map_err(|_| ParseGasError::InvalidNumber(s.to_string()))?;
530 return Ok(Self(v));
531 }
532
533 Err(ParseGasError::InvalidFormat(s.to_string()))
534 }
535}
536
537impl TryFrom<&str> for Gas {
538 type Error = ParseGasError;
539
540 fn try_from(s: &str) -> Result<Self, Self::Error> {
541 s.parse()
542 }
543}
544
545impl Display for Gas {
546 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
547 let tgas = self.0 / GAS_PER_TGAS;
548 if tgas > 0 && self.0 % GAS_PER_TGAS == 0 {
549 write!(f, "{} Tgas", tgas)
550 } else {
551 write!(f, "{} gas", self.0)
552 }
553 }
554}
555
556impl Add for Gas {
557 type Output = Self;
558
559 fn add(self, other: Self) -> Self {
560 Self(self.0 + other.0)
561 }
562}
563
564impl Sub for Gas {
565 type Output = Self;
566
567 fn sub(self, other: Self) -> Self {
568 Self(self.0 - other.0)
569 }
570}
571
572impl Serialize for Gas {
573 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
574 s.serialize_u64(self.0)
575 }
576}
577
578impl<'de> Deserialize<'de> for Gas {
579 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
580 let v: u64 = serde::Deserialize::deserialize(d)?;
581 Ok(Self(v))
582 }
583}
584
585impl BorshSerialize for Gas {
586 fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
587 borsh::BorshSerialize::serialize(&self.0, writer)
588 }
589}
590
591impl BorshDeserialize for Gas {
592 fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
593 Ok(Self(u64::deserialize_reader(reader)?))
594 }
595}
596
597pub trait IntoNearToken {
622 fn into_near_token(self) -> Result<NearToken, ParseAmountError>;
624}
625
626impl IntoNearToken for NearToken {
627 fn into_near_token(self) -> Result<NearToken, ParseAmountError> {
628 Ok(self)
629 }
630}
631
632impl IntoNearToken for &str {
633 fn into_near_token(self) -> Result<NearToken, ParseAmountError> {
634 self.parse()
635 }
636}
637
638impl IntoNearToken for String {
639 fn into_near_token(self) -> Result<NearToken, ParseAmountError> {
640 self.parse()
641 }
642}
643
644impl IntoNearToken for &String {
645 fn into_near_token(self) -> Result<NearToken, ParseAmountError> {
646 self.parse()
647 }
648}
649
650pub trait IntoGas {
675 fn into_gas(self) -> Result<Gas, ParseGasError>;
677}
678
679impl IntoGas for Gas {
680 fn into_gas(self) -> Result<Gas, ParseGasError> {
681 Ok(self)
682 }
683}
684
685impl IntoGas for &str {
686 fn into_gas(self) -> Result<Gas, ParseGasError> {
687 self.parse()
688 }
689}
690
691impl IntoGas for String {
692 fn into_gas(self) -> Result<Gas, ParseGasError> {
693 self.parse()
694 }
695}
696
697impl IntoGas for &String {
698 fn into_gas(self) -> Result<Gas, ParseGasError> {
699 self.parse()
700 }
701}
702
703#[cfg(test)]
704mod tests {
705 use super::*;
706
707 #[test]
712 fn test_near_token_parsing() {
713 assert_eq!(
714 "5 NEAR".parse::<NearToken>().unwrap().as_yoctonear(),
715 5 * YOCTO_PER_NEAR
716 );
717 assert_eq!(
718 "1.5 NEAR".parse::<NearToken>().unwrap().as_yoctonear(),
719 YOCTO_PER_NEAR + YOCTO_PER_NEAR / 2
720 );
721 assert_eq!(
722 "100 milliNEAR".parse::<NearToken>().unwrap().as_yoctonear(),
723 100 * YOCTO_PER_MILLINEAR
724 );
725 assert_eq!(
726 "1000 yocto".parse::<NearToken>().unwrap().as_yoctonear(),
727 1000
728 );
729 }
730
731 #[test]
732 fn test_near_token_display() {
733 assert_eq!(NearToken::ZERO.to_string(), "0 NEAR");
734 assert_eq!(NearToken::from_near(5).to_string(), "5 NEAR");
735 assert_eq!(NearToken::from_near(100).to_string(), "100 NEAR");
736 }
737
738 #[test]
739 fn test_near_token_ambiguous() {
740 assert!(matches!(
741 "123".parse::<NearToken>(),
742 Err(ParseAmountError::AmbiguousAmount(_))
743 ));
744 }
745
746 #[test]
747 fn test_gas_parsing() {
748 assert_eq!(
749 "30 Tgas".parse::<Gas>().unwrap().as_gas(),
750 30 * GAS_PER_TGAS
751 );
752 assert_eq!("5 Ggas".parse::<Gas>().unwrap().as_gas(), 5 * GAS_PER_GGAS);
753 assert_eq!("1000 gas".parse::<Gas>().unwrap().as_gas(), 1000);
754 }
755
756 #[test]
757 fn test_gas_display() {
758 assert_eq!(Gas::from_tgas(30).to_string(), "30 Tgas");
759 assert_eq!(Gas::from_gas(1000).to_string(), "1000 gas");
760 }
761
762 #[test]
763 fn test_gas_default() {
764 assert_eq!(Gas::DEFAULT.as_tgas(), 30);
765 }
766
767 #[test]
772 fn test_near_token_constructors() {
773 assert_eq!(NearToken::near(5).as_yoctonear(), 5 * YOCTO_PER_NEAR);
775 assert_eq!(
776 NearToken::millinear(500).as_yoctonear(),
777 500 * YOCTO_PER_MILLINEAR
778 );
779 assert_eq!(NearToken::yocto(1000).as_yoctonear(), 1000);
780
781 assert_eq!(NearToken::from_near(5).as_yoctonear(), 5 * YOCTO_PER_NEAR);
783 assert_eq!(
784 NearToken::from_millinear(500).as_yoctonear(),
785 500 * YOCTO_PER_MILLINEAR
786 );
787 assert_eq!(NearToken::from_yoctonear(1000).as_yoctonear(), 1000);
788 }
789
790 #[test]
791 fn test_near_token_constants() {
792 assert_eq!(NearToken::ZERO.as_yoctonear(), 0);
793 assert_eq!(NearToken::ONE_YOCTO.as_yoctonear(), 1);
794 assert_eq!(NearToken::ONE_MILLINEAR.as_yoctonear(), YOCTO_PER_MILLINEAR);
795 assert_eq!(NearToken::ONE_NEAR.as_yoctonear(), YOCTO_PER_NEAR);
796 }
797
798 #[test]
799 fn test_near_token_as_near() {
800 assert_eq!(NearToken::near(5).as_near(), 5);
801 assert_eq!(NearToken::millinear(500).as_near(), 0); assert_eq!(NearToken::millinear(1500).as_near(), 1); }
804
805 #[test]
806 fn test_near_token_as_near_f64() {
807 let amount = NearToken::millinear(500);
808 let f64_val = amount.as_near_f64();
809 assert!((f64_val - 0.5).abs() < 0.0001);
810 }
811
812 #[test]
813 fn test_near_token_is_zero() {
814 assert!(NearToken::ZERO.is_zero());
815 assert!(!NearToken::ONE_YOCTO.is_zero());
816 }
817
818 #[test]
823 fn test_near_token_add() {
824 let a = NearToken::near(5);
825 let b = NearToken::near(3);
826 assert_eq!((a + b).as_near(), 8);
827 }
828
829 #[test]
830 fn test_near_token_sub() {
831 let a = NearToken::near(5);
832 let b = NearToken::near(3);
833 assert_eq!((a - b).as_near(), 2);
834 }
835
836 #[test]
837 fn test_near_token_checked_add() {
838 let a = NearToken::near(5);
839 let b = NearToken::near(3);
840 assert_eq!(a.checked_add(b).unwrap().as_near(), 8);
841
842 let max = NearToken::from_yoctonear(u128::MAX);
844 assert!(max.checked_add(NearToken::ONE_YOCTO).is_none());
845 }
846
847 #[test]
848 fn test_near_token_checked_sub() {
849 let a = NearToken::near(5);
850 let b = NearToken::near(3);
851 assert_eq!(a.checked_sub(b).unwrap().as_near(), 2);
852
853 assert!(b.checked_sub(a).is_none());
855 }
856
857 #[test]
858 fn test_near_token_saturating_add() {
859 let a = NearToken::near(5);
860 let b = NearToken::near(3);
861 assert_eq!(a.saturating_add(b).as_near(), 8);
862
863 let max = NearToken::from_yoctonear(u128::MAX);
865 assert_eq!(max.saturating_add(NearToken::ONE_YOCTO), max);
866 }
867
868 #[test]
869 fn test_near_token_saturating_sub() {
870 let a = NearToken::near(5);
871 let b = NearToken::near(3);
872 assert_eq!(a.saturating_sub(b).as_near(), 2);
873
874 assert_eq!(b.saturating_sub(a), NearToken::ZERO);
876 }
877
878 #[test]
883 fn test_near_token_parse_lowercase() {
884 assert_eq!("5 near".parse::<NearToken>().unwrap().as_near(), 5);
885 }
886
887 #[test]
888 fn test_near_token_parse_mnear() {
889 assert_eq!(
890 "100 mNEAR".parse::<NearToken>().unwrap().as_yoctonear(),
891 100 * YOCTO_PER_MILLINEAR
892 );
893 }
894
895 #[test]
896 fn test_near_token_parse_yoctonear() {
897 assert_eq!(
898 "12345 yoctoNEAR"
899 .parse::<NearToken>()
900 .unwrap()
901 .as_yoctonear(),
902 12345
903 );
904 }
905
906 #[test]
907 fn test_near_token_parse_decimal_near() {
908 assert_eq!(
909 "0.5 NEAR".parse::<NearToken>().unwrap().as_yoctonear(),
910 YOCTO_PER_NEAR / 2
911 );
912 assert_eq!(
913 ".25 NEAR".parse::<NearToken>().unwrap().as_yoctonear(),
914 YOCTO_PER_NEAR / 4
915 );
916 }
917
918 #[test]
919 fn test_near_token_parse_with_whitespace() {
920 assert_eq!(" 5 NEAR ".parse::<NearToken>().unwrap().as_near(), 5);
921 }
922
923 #[test]
924 fn test_near_token_parse_invalid_format() {
925 assert!(matches!(
926 "5 ETH".parse::<NearToken>(),
927 Err(ParseAmountError::InvalidFormat(_))
928 ));
929 }
930
931 #[test]
932 fn test_near_token_parse_invalid_number() {
933 assert!(matches!(
934 "abc NEAR".parse::<NearToken>(),
935 Err(ParseAmountError::InvalidNumber(_))
936 ));
937 }
938
939 #[test]
940 fn test_near_token_try_from_str() {
941 let token = NearToken::try_from("5 NEAR").unwrap();
942 assert_eq!(token.as_near(), 5);
943 }
944
945 #[test]
950 fn test_near_token_serde_roundtrip() {
951 let amount = NearToken::near(5);
952 let json = serde_json::to_string(&amount).unwrap();
953 assert_eq!(json, format!("\"{}\"", amount.as_yoctonear()));
955
956 let parsed: NearToken = serde_json::from_str(&json).unwrap();
957 assert_eq!(amount, parsed);
958 }
959
960 #[test]
961 fn test_near_token_borsh_roundtrip() {
962 let amount = NearToken::near(10);
963 let bytes = borsh::to_vec(&amount).unwrap();
964 let parsed: NearToken = borsh::from_slice(&bytes).unwrap();
965 assert_eq!(amount, parsed);
966 }
967
968 #[test]
973 fn test_near_token_display_fractional() {
974 let amount = NearToken::from_yoctonear(YOCTO_PER_NEAR + YOCTO_PER_NEAR / 2);
976 let display = amount.to_string();
977 assert!(display.contains("1.5") || display.contains("1."));
978 assert!(display.contains("NEAR"));
979 }
980
981 #[test]
986 fn test_near_token_ord() {
987 let small = NearToken::near(1);
988 let large = NearToken::near(10);
989 assert!(small < large);
990 assert!(large > small);
991 assert!(small <= small);
992 assert!(small >= small);
993 }
994
995 #[test]
996 fn test_near_token_eq() {
997 let a = NearToken::near(5);
998 let b = NearToken::millinear(5000);
999 assert_eq!(a, b);
1000 }
1001
1002 #[test]
1003 fn test_near_token_hash() {
1004 use std::collections::HashSet;
1005 let mut set = HashSet::new();
1006 set.insert(NearToken::near(1));
1007 set.insert(NearToken::near(2));
1008 assert!(set.contains(&NearToken::near(1)));
1009 assert!(!set.contains(&NearToken::near(3)));
1010 }
1011
1012 #[test]
1017 fn test_gas_constructors() {
1018 assert_eq!(Gas::tgas(30).as_gas(), 30 * GAS_PER_TGAS);
1019 assert_eq!(Gas::ggas(5).as_gas(), 5 * GAS_PER_GGAS);
1020 assert_eq!(Gas::from_gas(1000).as_gas(), 1000);
1021 assert_eq!(Gas::from_tgas(30).as_gas(), 30 * GAS_PER_TGAS);
1022 assert_eq!(Gas::from_ggas(5).as_gas(), 5 * GAS_PER_GGAS);
1023 }
1024
1025 #[test]
1026 fn test_gas_constants() {
1027 assert_eq!(Gas::ZERO.as_gas(), 0);
1028 assert_eq!(Gas::ONE_GGAS.as_gas(), GAS_PER_GGAS);
1029 assert_eq!(Gas::ONE_TGAS.as_gas(), GAS_PER_TGAS);
1030 assert_eq!(Gas::ONE_PGAS.as_gas(), GAS_PER_PGAS);
1031 assert_eq!(Gas::DEFAULT.as_tgas(), 30);
1032 assert_eq!(Gas::MAX.as_tgas(), 1_000);
1033 }
1034
1035 #[test]
1036 fn test_gas_as_accessors() {
1037 let gas = Gas::tgas(30);
1038 assert_eq!(gas.as_tgas(), 30);
1039 assert_eq!(gas.as_ggas(), 30_000);
1040 assert_eq!(gas.as_gas(), 30 * GAS_PER_TGAS);
1041 }
1042
1043 #[test]
1044 fn test_gas_is_zero() {
1045 assert!(Gas::ZERO.is_zero());
1046 assert!(!Gas::ONE_GGAS.is_zero());
1047 }
1048
1049 #[test]
1050 fn test_gas_add() {
1051 let a = Gas::tgas(10);
1052 let b = Gas::tgas(20);
1053 assert_eq!((a + b).as_tgas(), 30);
1054 }
1055
1056 #[test]
1057 fn test_gas_sub() {
1058 let a = Gas::tgas(30);
1059 let b = Gas::tgas(10);
1060 assert_eq!((a - b).as_tgas(), 20);
1061 }
1062
1063 #[test]
1064 fn test_gas_checked_add() {
1065 let a = Gas::tgas(10);
1066 let b = Gas::tgas(20);
1067 assert_eq!(a.checked_add(b).unwrap().as_tgas(), 30);
1068
1069 let max = Gas::from_gas(u64::MAX);
1071 assert!(max.checked_add(Gas::from_gas(1)).is_none());
1072 }
1073
1074 #[test]
1075 fn test_gas_checked_sub() {
1076 let a = Gas::tgas(30);
1077 let b = Gas::tgas(10);
1078 assert_eq!(a.checked_sub(b).unwrap().as_tgas(), 20);
1079
1080 assert!(b.checked_sub(a).is_none());
1082 }
1083
1084 #[test]
1085 fn test_gas_parse_tgas_variants() {
1086 assert_eq!("30 Tgas".parse::<Gas>().unwrap().as_tgas(), 30);
1087 assert_eq!("30 tgas".parse::<Gas>().unwrap().as_tgas(), 30);
1088 assert_eq!("30 TGas".parse::<Gas>().unwrap().as_tgas(), 30);
1089 }
1090
1091 #[test]
1092 fn test_gas_parse_ggas_variants() {
1093 assert_eq!("5 Ggas".parse::<Gas>().unwrap().as_ggas(), 5);
1094 assert_eq!("5 ggas".parse::<Gas>().unwrap().as_ggas(), 5);
1095 assert_eq!("5 GGas".parse::<Gas>().unwrap().as_ggas(), 5);
1096 }
1097
1098 #[test]
1099 fn test_gas_parse_invalid_format() {
1100 assert!(matches!(
1101 "30 teragas".parse::<Gas>(),
1102 Err(ParseGasError::InvalidFormat(_))
1103 ));
1104 }
1105
1106 #[test]
1107 fn test_gas_parse_invalid_number() {
1108 assert!(matches!(
1109 "abc Tgas".parse::<Gas>(),
1110 Err(ParseGasError::InvalidNumber(_))
1111 ));
1112 }
1113
1114 #[test]
1115 fn test_gas_try_from_str() {
1116 let gas = Gas::try_from("30 Tgas").unwrap();
1117 assert_eq!(gas.as_tgas(), 30);
1118 }
1119
1120 #[test]
1121 fn test_gas_serde_roundtrip() {
1122 let gas = Gas::tgas(30);
1123 let json = serde_json::to_string(&gas).unwrap();
1124 let parsed: Gas = serde_json::from_str(&json).unwrap();
1125 assert_eq!(gas, parsed);
1126 }
1127
1128 #[test]
1129 fn test_gas_borsh_roundtrip() {
1130 let gas = Gas::tgas(30);
1131 let bytes = borsh::to_vec(&gas).unwrap();
1132 let parsed: Gas = borsh::from_slice(&bytes).unwrap();
1133 assert_eq!(gas, parsed);
1134 }
1135
1136 #[test]
1137 fn test_gas_ord() {
1138 let small = Gas::tgas(10);
1139 let large = Gas::tgas(100);
1140 assert!(small < large);
1141 }
1142
1143 #[test]
1148 fn test_into_near_token_from_near_token() {
1149 let token = NearToken::near(5);
1150 assert_eq!(token.into_near_token().unwrap(), NearToken::near(5));
1151 }
1152
1153 #[test]
1154 fn test_into_near_token_from_str() {
1155 assert_eq!("5 NEAR".into_near_token().unwrap(), NearToken::near(5));
1156 }
1157
1158 #[test]
1159 fn test_into_near_token_from_string() {
1160 let s = String::from("5 NEAR");
1161 assert_eq!(s.into_near_token().unwrap(), NearToken::near(5));
1162 }
1163
1164 #[test]
1165 fn test_into_near_token_from_string_ref() {
1166 let s = String::from("5 NEAR");
1167 assert_eq!((&s).into_near_token().unwrap(), NearToken::near(5));
1168 }
1169
1170 #[test]
1175 fn test_into_gas_from_gas() {
1176 let gas = Gas::tgas(30);
1177 assert_eq!(gas.into_gas().unwrap(), Gas::tgas(30));
1178 }
1179
1180 #[test]
1181 fn test_into_gas_from_str() {
1182 assert_eq!("30 Tgas".into_gas().unwrap(), Gas::tgas(30));
1183 }
1184
1185 #[test]
1186 fn test_into_gas_from_string() {
1187 let s = String::from("30 Tgas");
1188 assert_eq!(s.into_gas().unwrap(), Gas::tgas(30));
1189 }
1190
1191 #[test]
1192 fn test_into_gas_from_string_ref() {
1193 let s = String::from("30 Tgas");
1194 assert_eq!((&s).into_gas().unwrap(), Gas::tgas(30));
1195 }
1196
1197 #[test]
1202 fn test_near_token_default() {
1203 let default = NearToken::default();
1204 assert_eq!(default, NearToken::ZERO);
1205 }
1206
1207 #[test]
1208 fn test_gas_default_trait() {
1209 let default = Gas::default();
1210 assert_eq!(default, Gas::ZERO);
1211 }
1212
1213 #[test]
1214 fn test_near_token_debug() {
1215 let token = NearToken::near(5);
1216 let debug = format!("{:?}", token);
1217 assert!(debug.contains("NearToken"));
1218 }
1219
1220 #[test]
1221 fn test_gas_debug() {
1222 let gas = Gas::tgas(30);
1223 let debug = format!("{:?}", gas);
1224 assert!(debug.contains("Gas"));
1225 }
1226
1227 #[test]
1228 fn test_gas_display_non_tgas_multiple() {
1229 let gas = Gas::from_gas(1500);
1231 assert_eq!(gas.to_string(), "1500 gas");
1232 }
1233}