stablesats-shared 0.1.3

Code shared between stablesats packages
Documentation
use rust_decimal::prelude::*;
use serde::{Deserialize, Serialize};

use crate::currency::*;

crate::string_wrapper! { ExchangeIdRaw }
crate::string_wrapper! { InstrumentIdRaw }
crate::string_wrapper! { CurrencyRaw }

const PRICE_IN_CENTS_PRECISION: u32 = 12;

#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PriceRatioRaw {
    pub numerator_unit: CurrencyRaw,
    pub denominator_unit: CurrencyRaw,
    pub(super) offset: u32,
    pub(super) base: Decimal,
}
impl PriceRatioRaw {
    pub fn from_one_btc_in_usd_price(price: Decimal) -> Self {
        let price_in_cents = price * Decimal::from(100);
        let price_with_precision =
            price_in_cents * Decimal::from(10_u64.pow(PRICE_IN_CENTS_PRECISION));
        let base = price_with_precision / Decimal::from(100_000_000);
        Self {
            numerator_unit: CurrencyRaw::from(UsdCents::code()),
            denominator_unit: CurrencyRaw::from(Sats::code()),
            offset: PRICE_IN_CENTS_PRECISION,
            base: base.trunc(),
        }
    }

    pub fn numerator_amount(&self) -> Decimal {
        let mut ret = self.base;
        ret.set_scale(self.offset).expect("failed to set scale");
        ret.normalize()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn serialize_ratio() {
        let ratio = PriceRatioRaw {
            numerator_unit: CurrencyRaw("USD".to_string()),
            denominator_unit: CurrencyRaw("BTC".to_string()),
            offset: 2,
            base: Decimal::new(123, 0),
        };
        let serialized = serde_json::to_string(&ratio).unwrap();

        assert_eq!(
            serialized,
            r#"{"numeratorUnit":"USD","denominatorUnit":"BTC","offset":2,"base":"123"}"#
        );
    }

    #[test]
    fn amount() {
        let ratio = PriceRatioRaw {
            numerator_unit: CurrencyRaw::from("USD"),
            denominator_unit: CurrencyRaw::from("BTC"),
            offset: 12,
            base: Decimal::new(123, 0),
        };
        let rate = ratio.numerator_amount();
        assert_eq!(rate.to_string(), "0.000000000123".to_string());
    }

    #[test]
    fn from_usd_btc_price() -> anyhow::Result<()> {
        let amount = "9999.99".parse::<Decimal>()?;
        let ratio = PriceRatioRaw::from_one_btc_in_usd_price(amount);

        assert_eq!(ratio.numerator_unit, CurrencyRaw::from("USD_CENT"));
        assert_eq!(ratio.denominator_unit, CurrencyRaw::from("SATOSHI"));

        assert_eq!(ratio.offset, 12);
        assert_eq!(&ratio.base.to_string(), "9999990000");
        assert_eq!(&ratio.numerator_amount().to_string(), "0.00999999");

        Ok(())
    }
}