use crate::error::FuelError;
use crate::fraction::Fraction;
use crate::time::Period;
use hdk::prelude::timestamp::Timestamp;
use regex::Regex;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::{
collections::VecDeque,
fmt,
ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign},
result,
str::FromStr,
};
pub const EXPONENT: usize = 18; pub const DENOMINATOR: i64 = 1_000_000_000_000_000_000;
pub const INTLIMIT: usize = 18; pub const HEXLIMIT: usize = 36;
pub const MAXVALUE: i128 = i128::max_value(); pub const MAXRANGE: u128 = MAXVALUE as u128;
pub const MINVALUE: i128 = -MAXVALUE - 1; pub const MINRANGE: u128 = MAXRANGE + 1;
pub const DECSHOWN: usize = 1;
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq)] pub struct Fuel {
pub units: i128,
}
pub type FuelResult = result::Result<Fuel, FuelError>;
impl Serialize for Fuel {
fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.to_string().as_ref())
}
}
impl<'d> Deserialize<'d> for Fuel {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'d>,
{
let s = String::deserialize(deserializer)?;
Fuel::from_str(&s).map_err(|e| de::Error::custom(e.to_string()))
}
}
impl Fuel {
pub fn new(units: i128) -> Self {
Fuel { units }
}
pub fn check(amount: &str) -> Result<bool, FuelError> {
lazy_static! {
static ref FUEL_RE: Regex = Regex::new( &format!( r"(?x)
^\s*
(?P<sig>[-+]?)
\s*
(?:
(?:0x(?P<hex>[a-fA-F0-9]{{1,{hexlimit}}}))
|(?:[H♓]?\s*
(?:
(?P<int>\d{{1,{intlimit}}})\.?
|(?P<mnt>\d{{0,{intlimit}}})\.(?P<frc>\d+)
)
)
)
\s*$", hexlimit = HEXLIMIT, intlimit = INTLIMIT )).unwrap();
}
let caps = FUEL_RE
.captures(amount)
.ok_or_else(|| FuelError::Range(format!("Invalid Holo fuel amount {}", amount)))?;
let sign = match caps.name("sig") {
Some(cap) => cap.as_str(),
None => "",
};
match sign {
"-" => Err(FuelError::Range(format!(
"Invalid negative amount {}",
amount
))),
_ => Ok(true),
}
}
}
pub fn u128_to_i128(
negative: bool,
mantissa: u128,
fraction: u128,
range: u128,
) -> Result<i128, FuelError> {
match mantissa.checked_add(fraction) {
Some(u_units) => {
if u_units > range {
Err(FuelError::Range(format!(
"Exceeded range for Holo fuel mantissa {}, fraction {}",
mantissa, fraction
)))
} else if negative {
if u_units > MINRANGE {
Err(FuelError::Range(format!(
"Underflow for Holo fuel negative mantissa {}, fraction {}",
mantissa, fraction
)))
} else if u_units == MINRANGE {
Ok(MINVALUE)
} else {
Ok(-(u_units as i128))
}
} else {
Ok(u_units as i128)
}
}
None => Err(FuelError::Range(format!(
"Overflow for Holo fuel mantissa {}, fraction {}",
mantissa, fraction
))),
}
}
impl FromStr for Fuel {
type Err = FuelError;
fn from_str(amount: &str) -> result::Result<Self, Self::Err> {
lazy_static! {
static ref FUEL_RE: Regex = Regex::new( &format!( r"(?x)
^\s*
(?P<sig>[-+]?)
\s*
(?:
(?:0x(?P<hex>[a-fA-F0-9]{{1,{hexlimit}}}))
|(?:[H♓]?\s*
(?:
(?P<int>\d{{1,{intlimit}}})\.?
|(?P<mnt>\d{{0,{intlimit}}})\.(?P<frc>\d+)
)
)
)
\s*$", hexlimit = HEXLIMIT, intlimit = INTLIMIT )).unwrap();
}
let caps = FUEL_RE
.captures(amount)
.ok_or_else(|| FuelError::Range(format!("Invalid Holo fuel amount {}", amount)))?;
let mantissa = match caps.name("hex") {
None => match caps.name("int") {
None => match caps.name("mnt") {
None => {
return Err(FuelError::Range(
format!("Invalid Holo fuel amount {}", amount),
));
}
Some(mnt) => match mnt.as_str().as_ref() {
"" => 0_u128,
mnt_str => {
DENOMINATOR as u128
* u128::from_str_radix(mnt_str, 10).or_else(|_| {
Err(FuelError::Range(format!(
"Invalid Holo fuel amount {}; bad mantissa {}",
amount,
mnt.as_str()
)))
})?
}
},
},
Some(int) => {
DENOMINATOR as u128
* u128::from_str_radix(int.as_str(), 10).or_else(|_| {
Err(FuelError::Range(format!(
"Invalid Holo fuel amount {}; bad int {}",
amount,
int.as_str()
)))
})?
}
},
Some(hex) => u128::from_str_radix(hex.as_str(), 16).or_else(|_| {
Err(FuelError::Range(format!(
"Invalid Holo fuel amount {}; bad hex {}",
amount,
hex.as_str()
)))
})?,
};
let fraction: u128 = match caps.name("frc") {
None => 0,
Some(fra) => u128::from_str_radix(
&format!(
"{:0<exponent$.exponent$}",
fra.as_str(),
exponent = EXPONENT
),
10,
)
.or_else(|_| {
Err(FuelError::Range(format!(
"Invalid Holo fuel amount {}; bad fraction {}",
amount,
fra.as_str()
)))
})?,
};
let sign = match caps.name("sig") {
Some(cap) => cap.as_str(),
None => "",
};
let units_res = match sign {
"-" => u128_to_i128(true, mantissa, fraction, MINRANGE), _ => u128_to_i128(false, mantissa, fraction, MAXRANGE), };
let amount_fuel = match units_res {
Ok(units) => Fuel { units },
Err(e) => return Err(e),
};
Ok(amount_fuel)
}
}
impl From<i128> for Fuel {
fn from(units: i128) -> Fuel {
Fuel { units }
}
}
impl From<&mut Fuel> for Fuel {
fn from(other: &mut Fuel) -> Fuel {
Fuel { units: other.units }
}
}
impl fmt::Display for Fuel {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let sign = if self.units < 0 { "-" } else { "" };
let whole = self.units / (DENOMINATOR as i128); let fraction = self.units - whole * (DENOMINATOR as i128); if fraction == 0 {
write!(f, "{}{}", sign, whole.abs())
} else {
let decimals = format!("{:0>exponent$}", fraction.abs(), exponent = EXPONENT);
let decimals = decimals.trim_end_matches('0'); write!(
f,
"{}{}.{:0<decshown$}",
sign,
whole.abs(),
decimals,
decshown = DECSHOWN
)
}
}
}
impl fmt::Debug for Fuel {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Fuel({})", self)
}
}
impl Neg for Fuel {
type Output = FuelResult;
fn neg(self) -> FuelResult {
Ok(match self.units.checked_neg() {
Some(units) => Fuel { units },
None => {
return Err(FuelError::Range(format!(
"Overflow in negation of Holo fuel amount {}",
self
)))
}
})
}
}
impl Neg for &Fuel {
type Output = FuelResult;
fn neg(self) -> FuelResult {
-*self
}
}
impl Add for Fuel {
type Output = FuelResult;
fn add(self, rhs: Fuel) -> Self::Output {
Ok(match self.units.checked_add(rhs.units) {
Some(units) => Fuel { units },
None => {
return Err(FuelError::Range(format!(
"Overflow in addition of Holo fuel amount {} + {}",
self, rhs
)))
}
})
}
}
impl Add<&Fuel> for Fuel {
type Output = FuelResult;
fn add(self, rhs: &Fuel) -> Self::Output {
self + *rhs
}
}
impl Add<Fuel> for &Fuel {
type Output = FuelResult;
fn add(self, rhs: Fuel) -> Self::Output {
*self + rhs
}
}
impl Add for &Fuel {
type Output = FuelResult;
fn add(self, rhs: &Fuel) -> Self::Output {
*self + *rhs
}
}
impl Add<FuelResult> for Fuel {
type Output = FuelResult;
fn add(self, other: Self::Output) -> Self::Output {
match other {
Ok(rhs) => self + rhs,
Err(rhs_e) => Err(rhs_e),
}
}
}
impl Add<FuelResult> for &Fuel {
type Output = FuelResult;
fn add(self, other: Self::Output) -> Self::Output {
match other {
Ok(rhs) => *self + rhs,
Err(rhs_e) => Err(rhs_e),
}
}
}
impl Add<&FuelResult> for Fuel {
type Output = FuelResult;
fn add(self, other: &Self::Output) -> Self::Output {
match other {
Ok(rhs) => self + *rhs,
Err(rhs_e) => Err(rhs_e.clone()),
}
}
}
impl Add<&FuelResult> for &Fuel {
type Output = FuelResult;
fn add(self, other: &Self::Output) -> Self::Output {
match other {
Ok(rhs) => *self + *rhs,
Err(rhs_e) => Err(rhs_e.clone()),
}
}
}
impl Add<Fuel> for FuelResult {
type Output = FuelResult;
fn add(self, rhs: Fuel) -> Self::Output {
match self {
Ok(lhs) => lhs + rhs,
Err(lhs_e) => Err(lhs_e),
}
}
}
impl Add<&Fuel> for FuelResult {
type Output = FuelResult;
fn add(self, rhs: &Fuel) -> Self::Output {
match self {
Ok(lhs) => lhs + *rhs,
Err(lhs_e) => Err(lhs_e),
}
}
}
impl Add<Fuel> for &FuelResult {
type Output = FuelResult;
fn add(self, rhs: Fuel) -> Self::Output {
match self {
Ok(lhs) => *lhs + rhs,
Err(lhs_e) => Err(lhs_e.clone()),
}
}
}
impl Add<&Fuel> for &FuelResult {
type Output = FuelResult;
fn add(self, rhs: &Fuel) -> Self::Output {
match self {
Ok(lhs) => *lhs + *rhs,
Err(lhs_e) => Err(lhs_e.clone()),
}
}
}
impl AddAssign<Fuel> for FuelResult {
fn add_assign(&mut self, rhs: Fuel) {
*self = match self {
Ok(lhs) => Fuel::from(lhs) + rhs,
Err(lhs_e) => Err(lhs_e.clone()),
};
}
}
impl AddAssign<&Fuel> for FuelResult {
fn add_assign(&mut self, rhs: &Fuel) {
*self = match self {
Ok(lhs) => Fuel::from(lhs) + rhs,
Err(lhs_e) => Err(lhs_e.clone()),
};
}
}
impl Sub for Fuel {
type Output = FuelResult;
fn sub(self, rhs: Fuel) -> Self::Output {
Ok(match self.units.checked_sub(rhs.units) {
Some(units) => Fuel { units },
None => {
return Err(FuelError::Range(format!(
"Overflow in subtraction of Holo fuel amount {} - {}",
self, rhs
)))
}
})
}
}
impl Sub<&Fuel> for Fuel {
type Output = FuelResult;
fn sub(self, rhs: &Fuel) -> Self::Output {
self - *rhs
}
}
impl Sub<Fuel> for &Fuel {
type Output = FuelResult;
fn sub(self, rhs: Fuel) -> Self::Output {
*self - rhs
}
}
impl Sub for &Fuel {
type Output = FuelResult;
fn sub(self, rhs: &Fuel) -> Self::Output {
*self - *rhs
}
}
impl Sub<FuelResult> for Fuel {
type Output = FuelResult;
fn sub(self, other: Self::Output) -> Self::Output {
match other {
Ok(rhs) => self - rhs,
Err(rhs_e) => Err(rhs_e),
}
}
}
impl Sub<FuelResult> for &Fuel {
type Output = FuelResult;
fn sub(self, other: Self::Output) -> Self::Output {
match other {
Ok(rhs) => *self - rhs,
Err(rhs_e) => Err(rhs_e),
}
}
}
impl Sub<&FuelResult> for Fuel {
type Output = FuelResult;
fn sub(self, other: &Self::Output) -> Self::Output {
match other {
Ok(rhs) => self - *rhs,
Err(rhs_e) => Err(rhs_e.clone()),
}
}
}
impl Sub<&FuelResult> for &Fuel {
type Output = FuelResult;
fn sub(self, other: &Self::Output) -> Self::Output {
match other {
Ok(rhs) => *self - *rhs,
Err(rhs_e) => Err(rhs_e.clone()),
}
}
}
impl Sub<Fuel> for FuelResult {
type Output = FuelResult;
fn sub(self, rhs: Fuel) -> Self::Output {
match self {
Ok(lhs) => lhs - rhs,
Err(lhs_e) => Err(lhs_e),
}
}
}
impl Sub<&Fuel> for FuelResult {
type Output = FuelResult;
fn sub(self, rhs: &Fuel) -> Self::Output {
match self {
Ok(lhs) => lhs - *rhs,
Err(lhs_e) => Err(lhs_e),
}
}
}
impl Sub<Fuel> for &FuelResult {
type Output = FuelResult;
fn sub(self, rhs: Fuel) -> Self::Output {
match self {
Ok(lhs) => *lhs - rhs,
Err(lhs_e) => Err(lhs_e.clone()),
}
}
}
impl Sub<&Fuel> for &FuelResult {
type Output = FuelResult;
fn sub(self, rhs: &Fuel) -> Self::Output {
match self {
Ok(lhs) => *lhs - *rhs,
Err(lhs_e) => Err(lhs_e.clone()),
}
}
}
impl SubAssign<Fuel> for FuelResult {
fn sub_assign(&mut self, rhs: Fuel) {
*self = match self {
Ok(lhs) => Fuel::from(lhs) - rhs,
Err(lhs_e) => Err(lhs_e.clone()),
};
}
}
impl SubAssign<&Fuel> for FuelResult {
fn sub_assign(&mut self, rhs: &Fuel) {
*self = match self {
Ok(lhs) => Fuel::from(lhs) - rhs,
Err(lhs_e) => Err(lhs_e.clone()),
};
}
}
impl Mul<Fraction> for Fuel {
type Output = FuelResult;
fn mul(self, rhs: Fraction) -> Self::Output {
match self.units.checked_div(rhs.denominator) {
Some(quotient) => match quotient.checked_mul(rhs.numerator) {
Some(units) => match self
.units
.checked_rem(rhs.denominator)
.and_then(|e| e.checked_mul(rhs.numerator))
.and_then(|e| {
if e >= 0 {
e.checked_add(rhs.denominator - 1)
} else {
e.checked_sub(rhs.denominator - 1)
}
})
.and_then(|e| e.checked_div(rhs.denominator))
{
Some(extra) => Ok(Fuel {
units: units + extra,
}),
None => Err(FuelError::FractionOverflow((self, rhs))),
},
None => Err(FuelError::FractionOverflow((self, rhs))),
},
None => Err(FuelError::FractionOverflow((self, rhs))),
}
}
}
impl Mul<&Fraction> for Fuel {
type Output = FuelResult;
fn mul(self, rhs: &Fraction) -> Self::Output {
self * *rhs
}
}
impl Mul<Fraction> for &Fuel {
type Output = FuelResult;
fn mul(self, rhs: Fraction) -> Self::Output {
*self * rhs
}
}
impl Mul<&Fraction> for &Fuel {
type Output = FuelResult;
fn mul(self, rhs: &Fraction) -> Self::Output {
*self * *rhs
}
}
impl MulAssign<Fraction> for FuelResult {
fn mul_assign(&mut self, rhs: Fraction) {
*self = match self {
Ok(lhs) => Fuel::from(lhs) * rhs,
Err(lhs_e) => Err(lhs_e.clone()),
};
}
}
impl MulAssign<&Fraction> for FuelResult {
fn mul_assign(&mut self, rhs: &Fraction) {
*self = match self {
Ok(lhs) => Fuel::from(lhs) * rhs,
Err(lhs_e) => Err(lhs_e.clone()),
};
}
}
impl Div<Fraction> for Fuel {
type Output = FuelResult;
fn div(self, rhs: Fraction) -> Self::Output {
self * Fraction {
numerator: rhs.denominator,
denominator: rhs.numerator,
}
}
}
impl Div<&Fraction> for Fuel {
type Output = FuelResult;
fn div(self, rhs: &Fraction) -> Self::Output {
self / *rhs
}
}
impl Div<Fraction> for &Fuel {
type Output = FuelResult;
fn div(self, rhs: Fraction) -> Self::Output {
*self / rhs
}
}
impl Div<&Fraction> for &Fuel {
type Output = FuelResult;
fn div(self, rhs: &Fraction) -> Self::Output {
*self / *rhs
}
}
#[derive(Deserialize, Debug, Serialize, Clone, PartialEq, Eq)]
pub struct Delta(pub Timestamp, pub Fuel);
#[derive(Deserialize, Debug, Serialize, Clone, PartialEq, Eq)]
pub struct Limit {
pub amount: Option<Fuel>, pub period: Option<Period>, #[serde(skip_deserializing, skip_serializing_if = "VecDeque::is_empty")]
pub recent: VecDeque<Delta>, }
impl fmt::Display for Limit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.period {
None => match &self.amount {
None => write!(f, "(agent denied)"),
Some(amount) => write!(f, "♓{} / tx", amount),
},
Some(period) => match &self.amount {
None => write!(f, "1 tx / {}", period),
Some(amount) => write!(f, "♓{} / {}", amount, period),
},
}
}
}
impl Limit {
pub fn allow(&self) -> Result<(), FuelError> {
if self.period.is_none() && self.amount.is_none() {
Err(FuelError::AgentDenied(self.to_owned()))
} else {
Ok(())
}
}
}
#[cfg(test)]
pub mod tests {
use crate::fuel::{self, u128_to_i128, Fuel, FuelResult};
use std::str::FromStr;
#[test]
fn fuel_smoke_test() {
let f1 = Fuel::from_str("1.0").unwrap();
assert_eq!(f1.units, 1 * fuel::DENOMINATOR as i128);
let d1 = format!("{}", f1);
assert_eq!(
d1,
match fuel::DECSHOWN {
1 => "1",
2 => "1",
3 => "1",
4 => "1",
5 => "1",
6 => "1",
7 => "1",
8 => "1",
_ => "unknown",
}
);
let f2 = Fuel::from(-1234567890987654321);
assert_eq!(f2.units, -1234567890987654321_i128);
let d2 = format!("{}", f2);
assert_eq!(
d2,
match fuel::DECSHOWN {
6 => "-1234.567890",
_ => "-1.234567890987654321",
}
);
let f3 = Fuel::from_str("999.5").unwrap();
assert_eq!(f3.units, 999_500_000_000_000_000_000_i128);
let d3 = format!("{}", f3);
assert_eq!(
d3,
match fuel::DECSHOWN {
1 => "999.5",
2 => "999.50",
3 => "999.500",
4 => "999.5000",
5 => "999.50000",
6 => "999.500000",
7 => "999.5000000",
8 => "999.50000000",
_ => "unknown",
}
);
let f4 = Fuel::from_str("-1234.5678901234567890123456").unwrap();
assert_eq!(f4.units, -1234567890123456789012);
let d4 = format!("{}", f4);
assert_eq!(d4, "-1234.567890123456789012");
assert_eq!(
fuel::MAXVALUE,
170_141_183_460_469_231_731_687_303_715_884_105_727
);
assert_eq!(
fuel::MAXRANGE,
170_141_183_460_469_231_731_687_303_715_884_105_727
);
assert_eq!(
fuel::MINVALUE,
-170_141_183_460_469_231_731_687_303_715_884_105_728
);
assert_eq!(
fuel::MINRANGE,
170_141_183_460_469_231_731_687_303_715_884_105_728
);
assert_eq!(
format!(
"{:?}",
Fuel::from_str(&"-1701411834604692.31731687303715884105728")
),
"Ok(Fuel(-1701411834604692.317316873037158841))"
);
assert_eq!(
format!(
"{:?}",
Fuel::from_str(&"-1701411834604692.31731687303715884105728")
),
"Ok(Fuel(-1701411834604692.317316873037158841))"
);
match Fuel::from_str( &"0x80000000000000000000000000000000" ) { Ok(f) => panic!( "Expected failure due to fuel::MAXRANGE did not occur: ♓{}", f ),
Err(e) => assert_eq!( format!("{}", e ),
"HoloFuel Range Error: Exceeded range for Holo fuel mantissa 170141183460469231731687303715884105728, fraction 0" ),
}
assert_eq!(
format!("{:?}", Fuel::from_str(&"9999999999999999.9999999999999999")),
"Ok(Fuel(9999999999999999.9999999999999999))"
);
assert_eq!(
format!(
"{:?}",
Fuel::from_str(&"-9999999999999999.9999999999999999")
),
"Ok(Fuel(-9999999999999999.9999999999999999))"
);
match Fuel::from_str(&"1_000_000_000_000_000_000") {
Ok(f) => panic!(
"Expected failure due to fuel::MINVRANGE did not occur: ♓{}",
f
),
Err(e) => assert_eq!(
format!("{}", e),
"HoloFuel Range Error: Invalid Holo fuel amount 1_000_000_000_000_000_000"
),
}
}
#[test]
fn fuel_operators() {
let sum = Fuel::from_str("1.23").unwrap() + Fuel::from_str("-1000").unwrap();
match &sum {
Ok(ref f) => assert_eq!(format!("{}", f), "-998.77"),
Err(e) => panic!("Expected success, not {}", e),
}
let sum2 = sum + Fuel::from_str("100").unwrap();
match &sum2 {
Ok(ref f) => assert_eq!(format!("{}", f), "-898.77"),
Err(e) => panic!("Expected success, not {}", e),
}
let sum3 = Fuel::from_str("-1111.23").unwrap() + sum2;
match &sum3 {
Ok(ref f) => assert_eq!(format!("{}", f), "-2010"),
Err(e) => panic!("Expected success, not {}", e),
}
match Fuel::from(1_000_000_000_000_000_000) + Fuel::from(1) {
Ok(f) => assert_eq!(format!("{}", f), "1.000000000000000001"),
Err(e) => panic!("Expected success, not {}", e),
}
match Fuel::from(1_000_000_000_000_000_000) + &Fuel::from(2) {
Ok(f) => assert_eq!(format!("{}", f), "1.000000000000000002"),
Err(e) => panic!("Expected success, not {}", e),
}
match &Fuel::from(2_000_000_000_000_000_000) + Fuel::from(1) {
Ok(f) => assert_eq!(format!("{}", f), "2.000000000000000001"),
Err(e) => panic!("Expected success, not {}", e),
}
match &Fuel::from(2_000_000_000_000_000_000) + &Fuel::from(2) {
Ok(f) => assert_eq!(format!("{}", f), "2.000000000000000002"),
Err(e) => panic!("Expected success, not {}", e),
}
match Fuel::from_str("1") + Fuel::from(1) {
Ok(f) => assert_eq!(format!("{}", f), "1.000000000000000001"),
Err(e) => panic!("Expected success, not {}", e),
}
match Fuel::from_str("1") + &Fuel::from(1) {
Ok(f) => assert_eq!(format!("{}", f), "1.000000000000000001"),
Err(e) => panic!("Expected success, not {}", e),
}
match &Fuel::from_str("1") + Fuel::from(1) {
Ok(f) => assert_eq!(format!("{}", f), "1.000000000000000001"),
Err(e) => panic!("Expected success, not {}", e),
}
match &Fuel::from_str("1") + &Fuel::from(1) {
Ok(f) => assert_eq!(format!("{}", f), "1.000000000000000001"),
Err(e) => panic!("Expected success, not {}", e),
}
match Fuel::from(1) + Fuel::from_str("1") {
Ok(f) => assert_eq!(format!("{}", f), "1.000000000000000001"),
Err(e) => panic!("Expected success, not {}", e),
}
match &Fuel::from(1) + Fuel::from_str("1") {
Ok(f) => assert_eq!(format!("{}", f), "1.000000000000000001"),
Err(e) => panic!("Expected success, not {}", e),
}
match Fuel::from(1) + &Fuel::from_str("1") {
Ok(f) => assert_eq!(format!("{}", f), "1.000000000000000001"),
Err(e) => panic!("Expected success, not {}", e),
}
match &Fuel::from(1) + &Fuel::from_str("1") {
Ok(f) => assert_eq!(format!("{}", f), "1.000000000000000001"),
Err(e) => panic!("Expected success, not {}", e),
}
let mut fa = Fuel::from_str("1");
fa += Fuel::from(1);
match fa {
Ok(f) => assert_eq!(format!("{}", f), "1.000000000000000001"),
Err(e) => panic!("Expected success, not {}", e),
}
fa += &Fuel::from(-2);
match fa {
Ok(f) => assert_eq!(format!("{}", f), "0.999999999999999999"),
Err(e) => panic!("Expected success, not {}", e),
}
match Fuel::from(1_000_000_000_000_000_000) - Fuel::from(1) {
Ok(f) => assert_eq!(format!("{}", f), "0.999999999999999999"),
Err(e) => panic!("Expected success, not {}", e),
}
match Fuel::from(1_000_000_000_000_000_000) - &Fuel::from(2) {
Ok(f) => assert_eq!(format!("{}", f), "0.999999999999999998"),
Err(e) => panic!("Expected success, not {}", e),
}
match &Fuel::from(2_000_000_000_000_000_000) - Fuel::from(1) {
Ok(f) => assert_eq!(format!("{}", f), "1.999999999999999999"),
Err(e) => panic!("Expected success, not {}", e),
}
match &Fuel::from(2_000_000_000_000_000_000) - &Fuel::from(2) {
Ok(f) => assert_eq!(format!("{}", f), "1.999999999999999998"),
Err(e) => panic!("Expected success, not {}", e),
}
match Fuel::from_str("1") - Fuel::from(1) {
Ok(f) => assert_eq!(format!("{}", f), "0.999999999999999999"),
Err(e) => panic!("Expected success, not {}", e),
}
match Fuel::from_str("1") - &Fuel::from(1) {
Ok(f) => assert_eq!(format!("{}", f), "0.999999999999999999"),
Err(e) => panic!("Expected success, not {}", e),
}
match &Fuel::from_str("1") - Fuel::from(1) {
Ok(f) => assert_eq!(format!("{}", f), "0.999999999999999999"),
Err(e) => panic!("Expected success, not {}", e),
}
match &Fuel::from_str("1") - &Fuel::from(1) {
Ok(f) => assert_eq!(format!("{}", f), "0.999999999999999999"),
Err(e) => panic!("Expected success, not {}", e),
}
match Fuel::from(1) - Fuel::from_str("1") {
Ok(f) => assert_eq!(format!("{}", f), "-0.999999999999999999"),
Err(e) => panic!("Expected success, not {}", e),
}
match &Fuel::from(1) - Fuel::from_str("1") {
Ok(f) => assert_eq!(format!("{}", f), "-0.999999999999999999"),
Err(e) => panic!("Expected success, not {}", e),
}
match Fuel::from(1) - &Fuel::from_str("1") {
Ok(f) => assert_eq!(format!("{}", f), "-0.999999999999999999"),
Err(e) => panic!("Expected success, not {}", e),
}
match &Fuel::from(1) - &Fuel::from_str("1") {
Ok(f) => assert_eq!(format!("{}", f), "-0.999999999999999999"),
Err(e) => panic!("Expected success, not {}", e),
}
let mut fa = Fuel::from_str("1");
fa -= Fuel::from(1);
match fa {
Ok(f) => assert_eq!(format!("{}", f), "0.999999999999999999"),
Err(e) => panic!("Expected success, not {}", e),
}
fa -= &Fuel::from(2_000_000_000_000_000_001);
match fa {
Ok(f) => assert_eq!(format!("{}", f), "-1.000000000000000002"),
Err(e) => panic!("Expected success, not {}", e),
}
fa -= &Fuel::from(-2_000_000_000_000_000_003);
match fa {
Ok(f) => assert_eq!(format!("{}", f), "1.000000000000000001"),
Err(e) => panic!("Expected success, not {}", e),
}
match Fuel::from( u128_to_i128( true, fuel::MINRANGE, 0_u128, fuel::MINRANGE ).unwrap() ) - Fuel::from( 1 ) {
Ok(f) => panic!( "Expected failure, not {}", f ),
Err(e) => assert_eq!( format!( "{}", e ),
"HoloFuel Range Error: Overflow in subtraction of Holo fuel amount -170141183460469231731.687303715884105728 - 0.000000000000000001" ),
}
match Fuel::from( u128_to_i128( true, fuel::MINRANGE, 0_u128, fuel::MINRANGE ).unwrap() ) - Fuel::from( 1 ) + Fuel::from( 1 ) {
Ok(f) => panic!( "Expected failure, not {}", f ),
Err(e) => assert_eq!( format!( "{}", e ),
"HoloFuel Range Error: Overflow in subtraction of Holo fuel amount -170141183460469231731.687303715884105728 - 0.000000000000000001" ),
}
assert_eq!(
format!("{}", (-Fuel::from(fuel::MAXVALUE)).unwrap()),
"-170141183460469231731.687303715884105727"
);
assert_eq!(
format!("{}", (-&Fuel::from(fuel::MAXVALUE)).unwrap()),
"-170141183460469231731.687303715884105727"
);
assert_eq!(
format!("{:?}", -&Fuel::from(fuel::MINVALUE)),
"Err(Range(\"Overflow in negation of Holo fuel amount -170141183460469231731.687303715884105728\"))"
);
}
#[test]
fn fuel_comparisons() {
assert_eq!(Fuel::from(1_000_001) > Fuel::from(1_000_000), true);
assert_eq!(Fuel::from(1_000_001) < Fuel::from(1_000_000), false);
assert_eq!(Fuel::from(1_000_000) < Fuel::from(1_000_001), true);
assert_eq!(Fuel::from(1_000_000) == Fuel::from(1_000_001), false);
assert_eq!(Fuel::from(1_000_000) <= Fuel::from(1_000_001), true);
assert_eq!(Fuel::from(1_000_000) >= Fuel::from(1_000_001), false);
assert_eq!(Fuel::from(1_000_000) == Fuel::from(1_000_000), true);
assert_eq!(Fuel::from(1_000_000) <= Fuel::from(1_000_000), true);
assert_eq!(Fuel::from(1_000_000) >= Fuel::from(1_000_000), true);
}
use crate::fraction::Fraction;
#[test]
fn fuel_compute_fees() {
let feepct = Fraction::new(35, 1000).reduce();
assert_eq!((feepct.numerator, feepct.denominator), (7, 200));
let feeamt = Fuel { units: 399 } * &feepct;
match &feeamt {
Ok(ref f) => assert_eq!(format!("{}", f), "0.000000000000000014"), Err(e) => panic!("Expected success, not {}", e),
}
let inv_feepct = Fraction {
denominator: feepct.numerator,
numerator: feepct.denominator,
};
let feeamt = Fuel { units: 399 } / &inv_feepct;
match &feeamt {
Ok(ref f) => assert_eq!(format!("{}", f), "0.000000000000000014"),
Err(e) => panic!("Expected success, not {}", e),
}
let amount = Fuel {
units: fuel::MAXVALUE,
};
let feeamt = amount * &feepct;
match feeamt {
Ok(f) => assert_eq!(format!("{}", f), "5954941421116423110.609055630055943701"), Err(e) => panic!("Expected success, not {}", e),
};
match Fuel::new(fuel::MINVALUE) * &feepct {
Ok(f) => assert_eq!(format!("{}", f), "-5954941421116423110.609055630055943701"),
Err(e) => panic!("Expected success, not {}", e),
};
match Fuel::from(fuel::MAXVALUE) * Fraction::new(35, 1000).reduce() {
Ok(f) => assert_eq!(format!("{}", f), "5954941421116423110.609055630055943701"),
Err(e) => panic!("Expected success, not {}", e),
};
match &Fuel::from(fuel::MAXVALUE) * Fraction::new(35, 1000).reduce() {
Ok(f) => assert_eq!(format!("{}", f), "5954941421116423110.609055630055943701"),
Err(e) => panic!("Expected success, not {}", e),
};
match Fuel::from(fuel::MAXVALUE) * &Fraction::new(35, 1000).reduce() {
Ok(f) => assert_eq!(format!("{}", f), "5954941421116423110.609055630055943701"),
Err(e) => panic!("Expected success, not {}", e),
};
match &Fuel::from(fuel::MAXVALUE) * &Fraction::new(35, 1000).reduce() {
Ok(ref f) => assert_eq!(format!("{}", f), "5954941421116423110.609055630055943701"),
Err(e) => panic!("Expected success, not {}", e),
};
match Fuel::from(fuel::MAXVALUE) / Fraction::new(1000, 35).reduce() {
Ok(f) => assert_eq!(format!("{}", f), "5954941421116423110.609055630055943701"),
Err(e) => panic!("Expected success, not {}", e),
};
match &Fuel::from(fuel::MAXVALUE) / Fraction::new(1000, 35).reduce() {
Ok(f) => assert_eq!(format!("{}", f), "5954941421116423110.609055630055943701"),
Err(e) => panic!("Expected success, not {}", e),
};
match Fuel::from(fuel::MAXVALUE) / &Fraction::new(1000, 35).reduce() {
Ok(f) => assert_eq!(format!("{}", f), "5954941421116423110.609055630055943701"),
Err(e) => panic!("Expected success, not {}", e),
};
match &Fuel::from(fuel::MAXVALUE) / &Fraction::new(1000, 35).reduce() {
Ok(ref f) => assert_eq!(format!("{}", f), "5954941421116423110.609055630055943701"),
Err(e) => panic!("Expected success, not {}", e),
};
let mut feeamt: FuelResult = Ok(Fuel::from(fuel::MAXVALUE));
feeamt *= feepct;
match &feeamt {
Ok(ref f) => assert_eq!(format!("{}", f), "5954941421116423110.609055630055943701"),
Err(e) => panic!("Expected success, not {}", e),
};
assert_eq!(
format!(
"{}",
(Fuel::new(100) * Fraction::new(fuel::MAXVALUE, 2)).unwrap_err()
),
"HoloFuel overflow in ♓0.0000000000000001 * 170141183460469231731687303715884105727/2"
);
}
}