use std::str::FromStr;
use bigdecimal::{BigDecimal, RoundingMode, Zero};
use crate::api::TokenInfo;
use crate::error::{Result, ZeraError};
use crate::fees::{get_decimal_places_from_denomination, get_denomination_fallback};
pub(crate) fn to_decimal(value: impl AsRef<str>) -> Result<BigDecimal> {
BigDecimal::from_str(value.as_ref()).map_err(|error| {
ZeraError::Serialization(format!("Invalid decimal \"{}\": {error}", value.as_ref()))
})
}
pub(crate) fn decimal_from_f64(value: f64) -> Result<BigDecimal> {
to_decimal(value.to_string())
}
pub(crate) fn pow10(decimals: u32) -> BigDecimal {
if decimals == 0 {
BigDecimal::from(1u32)
} else {
BigDecimal::from_str(&format!("1{}", "0".repeat(decimals as usize))).expect("pow10")
}
}
pub(crate) fn floor_decimal(value: &BigDecimal) -> BigDecimal {
value.with_scale_round(0, RoundingMode::Down)
}
pub(crate) fn floor_decimal_to_string(value: &BigDecimal) -> String {
floor_decimal(value).normalized().to_string()
}
pub(crate) fn floor_to_scale(value: &BigDecimal, decimals: u32) -> BigDecimal {
value.with_scale_round(decimals as i64, RoundingMode::Down)
}
pub(crate) fn to_smallest_units(
amount: impl AsRef<str>,
contract_id: &str,
denomination: Option<&str>,
token_info_map: Option<&std::collections::HashMap<String, TokenInfo>>,
) -> Result<String> {
let amount = amount.as_ref();
if amount.is_empty() {
return Ok(String::new());
}
let decimals = if let Some(denomination) = denomination {
get_decimal_places_from_denomination(denomination)?
} else if let Some(token_info_map) = token_info_map {
let token = token_info_map.get(contract_id).ok_or_else(|| {
ZeraError::Validation(format!("Token info not found for {contract_id}"))
})?;
let denomination = if token.denomination.is_empty() {
get_denomination_fallback(contract_id)?
} else {
token.denomination.clone()
};
get_decimal_places_from_denomination(&denomination)?
} else {
let denomination = get_denomination_fallback(contract_id)?;
get_decimal_places_from_denomination(&denomination)?
};
let result = to_decimal(amount)? * pow10(decimals);
Ok(floor_decimal_to_string(&result))
}
pub(crate) fn add_amounts<'a>(values: impl IntoIterator<Item = &'a str>) -> Result<BigDecimal> {
let mut total = BigDecimal::zero();
for value in values {
total += to_decimal(value)?;
}
Ok(total)
}
pub(crate) fn validate_exact_amount_balance(
input_amounts: &[String],
output_amounts: &[String],
) -> Result<()> {
let total_inputs = add_amounts(input_amounts.iter().map(String::as_str))?;
let total_outputs = add_amounts(output_amounts.iter().map(String::as_str))?;
if total_inputs != total_outputs {
return Err(ZeraError::Validation(format!(
"Amount mismatch in coin transaction: inputs ({}) !== outputs ({}). Difference: {}",
total_inputs.normalized(),
total_outputs.normalized(),
(total_inputs - total_outputs).normalized()
)));
}
Ok(())
}