use borsh::{
BorshDeserialize,
BorshSerialize,
};
use hex::FromHexError;
use schemars::JsonSchema;
use std::fmt;
pub mod utils;
mod price;
pub use price::Price;
#[derive(
Copy,
Clone,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
BorshSerialize,
BorshDeserialize,
serde::Serialize,
serde::Deserialize,
JsonSchema,
)]
#[repr(C)]
pub struct Identifier(
#[serde(with = "hex")]
#[schemars(with = "String")]
[u8; 32],
);
impl Identifier {
pub fn new(bytes: [u8; 32]) -> Identifier {
Identifier(bytes)
}
pub fn to_bytes(&self) -> [u8; 32] {
self.0
}
pub fn to_hex(&self) -> String {
hex::encode(self.0)
}
pub fn from_hex<T: AsRef<[u8]>>(s: T) -> Result<Identifier, FromHexError> {
let mut bytes = [0u8; 32];
hex::decode_to_slice(s, &mut bytes)?;
Ok(Identifier::new(bytes))
}
}
impl fmt::Debug for Identifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0x{}", self.to_hex())
}
}
impl fmt::Display for Identifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0x{}", self.to_hex())
}
}
impl AsRef<[u8]> for Identifier {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
pub type PriceIdentifier = Identifier;
pub type ProductIdentifier = Identifier;
pub type UnixTimestamp = i64;
pub type DurationInSeconds = u64;
#[derive(
Copy,
Clone,
Debug,
Default,
PartialEq,
Eq,
BorshSerialize,
BorshDeserialize,
serde::Serialize,
serde::Deserialize,
JsonSchema,
)]
#[repr(C)]
pub struct PriceFeed {
pub id: PriceIdentifier,
price: Price,
ema_price: Price,
}
impl PriceFeed {
#[allow(clippy::too_many_arguments)]
pub fn new(id: PriceIdentifier, price: Price, ema_price: Price) -> PriceFeed {
PriceFeed {
id,
price,
ema_price,
}
}
pub fn get_price_unchecked(&self) -> Price {
self.price
}
pub fn get_ema_price_unchecked(&self) -> Price {
self.ema_price
}
pub fn get_price_no_older_than(
&self,
current_time: UnixTimestamp,
age: DurationInSeconds,
) -> Option<Price> {
let price = self.get_price_unchecked();
let time_diff_abs = (price.publish_time - current_time).abs() as u64;
if time_diff_abs > age {
return None;
}
Some(price)
}
pub fn get_ema_price_no_older_than(
&self,
current_time: UnixTimestamp,
age: DurationInSeconds,
) -> Option<Price> {
let price = self.get_ema_price_unchecked();
let time_diff_abs = (price.publish_time - current_time).abs() as u64;
if time_diff_abs > age {
return None;
}
Some(price)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
pub fn test_ser_then_deser_default() {
let price_feed = PriceFeed::default();
let ser = serde_json::to_string(&price_feed).unwrap();
let deser: PriceFeed = serde_json::from_str(&ser).unwrap();
assert_eq!(price_feed, deser);
}
#[test]
pub fn test_ser_large_number() {
let price_feed = PriceFeed {
ema_price: Price {
conf: 1_234_567_000_000_000_789,
..Price::default()
},
..PriceFeed::default()
};
let price_feed_json = serde_json::to_value(price_feed).unwrap();
assert_eq!(
price_feed_json["ema_price"]["conf"].as_str(),
Some("1234567000000000789")
);
}
#[test]
pub fn test_deser_large_number() {
let mut price_feed_json = serde_json::to_value(PriceFeed::default()).unwrap();
price_feed_json["price"]["price"] =
serde_json::Value::String(String::from("1000000000000000123"));
let p: PriceFeed = serde_json::from_value(price_feed_json).unwrap();
assert_eq!(p.get_price_unchecked().price, 1_000_000_000_000_000_123);
}
#[test]
pub fn test_ser_id_length_32_bytes() {
let mut price_feed = PriceFeed::default();
price_feed.id.0[0] = 106; let price_feed_json = serde_json::to_value(price_feed).unwrap();
let id_str = price_feed_json["id"].as_str().unwrap();
assert_eq!(id_str.len(), 64);
assert_eq!(
id_str,
"6a00000000000000000000000000000000000000000000000000000000000000"
);
}
#[test]
pub fn test_deser_invalid_id_length_fails() {
let mut price_feed_json = serde_json::to_value(PriceFeed::default()).unwrap();
price_feed_json["id"] = serde_json::Value::String(String::from("1234567890"));
assert!(serde_json::from_value::<PriceFeed>(price_feed_json).is_err());
}
#[test]
pub fn test_identifier_from_hex_ok() {
let id = Identifier::from_hex(
"0a3f000000000000000000000000000000000000000000000000000000000000",
)
.unwrap();
assert_eq!(id.to_bytes()[0], 10);
}
#[test]
pub fn test_identifier_from_hex_invalid_err() {
let try_parse_odd = Identifier::from_hex("010"); assert_eq!(try_parse_odd, Err(FromHexError::OddLength));
let try_parse_invalid_len = Identifier::from_hex("0a"); assert_eq!(
try_parse_invalid_len,
Err(FromHexError::InvalidStringLength)
);
}
#[test]
pub fn test_identifier_debug_fmt() {
let mut id = Identifier::default();
id.0[0] = 10;
let id_str = format!("{:?}", id);
assert_eq!(
id_str,
"0x0a00000000000000000000000000000000000000000000000000000000000000"
);
}
#[test]
pub fn test_identifier_display_fmt() {
let mut id = Identifier::default();
id.0[0] = 10;
let id_str = format!("{}", id);
assert_eq!(
id_str,
"0x0a00000000000000000000000000000000000000000000000000000000000000"
);
}
}