use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use serde::Serialize;
use solana_program::entrypoint::ProgramResult;
use crate::token::{self, Lamports, StLamports};
#[repr(C)]
#[derive(
Clone, Debug, Default, BorshDeserialize, BorshSerialize, BorshSchema, Eq, PartialEq, Serialize,
)]
pub struct Metrics {
#[serde(rename = "fee_treasury_total_lamports")]
pub fee_treasury_sol_total: Lamports,
#[serde(rename = "fee_validation_total_lamports")]
pub fee_validation_sol_total: Lamports,
#[serde(rename = "fee_developer_total_lamports")]
pub fee_developer_sol_total: Lamports,
#[serde(rename = "st_sol_appreciation_total_lamports")]
pub st_sol_appreciation_sol_total: Lamports,
#[serde(rename = "fee_treasury_total_st_lamports")]
pub fee_treasury_st_sol_total: StLamports,
#[serde(rename = "fee_validation_total_st_lamports")]
pub fee_validation_st_sol_total: StLamports,
#[serde(rename = "fee_developer_total_st_lamports")]
pub fee_developer_st_sol_total: StLamports,
pub deposit_amount: LamportsHistogram,
pub withdraw_amount: WithdrawMetric,
}
impl Metrics {
pub fn new() -> Self {
Self {
fee_treasury_sol_total: Lamports(0),
fee_validation_sol_total: Lamports(0),
fee_developer_sol_total: Lamports(0),
st_sol_appreciation_sol_total: Lamports(0),
fee_treasury_st_sol_total: StLamports(0),
fee_validation_st_sol_total: StLamports(0),
fee_developer_st_sol_total: StLamports(0),
deposit_amount: LamportsHistogram::new(),
withdraw_amount: WithdrawMetric::default(),
}
}
pub fn observe_fee_treasury(
&mut self,
amount_sol: Lamports,
amount_st_sol: StLamports,
) -> token::Result<()> {
self.fee_treasury_sol_total = (self.fee_treasury_sol_total + amount_sol)?;
self.fee_treasury_st_sol_total = (self.fee_treasury_st_sol_total + amount_st_sol)?;
Ok(())
}
pub fn observe_fee_validation(
&mut self,
amount_sol: Lamports,
amount_st_sol: StLamports,
) -> token::Result<()> {
self.fee_validation_sol_total = (self.fee_validation_sol_total + amount_sol)?;
self.fee_validation_st_sol_total = (self.fee_validation_st_sol_total + amount_st_sol)?;
Ok(())
}
pub fn observe_fee_developer(
&mut self,
amount_sol: Lamports,
amount_st_sol: StLamports,
) -> token::Result<()> {
self.fee_developer_sol_total = (self.fee_developer_sol_total + amount_sol)?;
self.fee_developer_st_sol_total = (self.fee_developer_st_sol_total + amount_st_sol)?;
Ok(())
}
pub fn observe_reward_st_sol_appreciation(&mut self, amount: Lamports) -> token::Result<()> {
self.st_sol_appreciation_sol_total = (self.st_sol_appreciation_sol_total + amount)?;
Ok(())
}
pub fn observe_deposit(&mut self, amount: Lamports) -> ProgramResult {
self.deposit_amount.observe(amount)
}
pub fn observe_withdrawal(
&mut self,
st_sol_amount: StLamports,
sol_amount: Lamports,
) -> token::Result<()> {
self.withdraw_amount.observe(st_sol_amount, sol_amount)
}
}
#[repr(C)]
#[derive(
Clone, Debug, Default, BorshDeserialize, BorshSerialize, BorshSchema, Eq, PartialEq, Serialize,
)]
pub struct LamportsHistogram {
pub counts: [u64; 12],
#[serde(rename = "total_lamports")]
pub total: Lamports,
}
impl LamportsHistogram {
#[rustfmt::skip]
pub const BUCKET_UPPER_BOUNDS: [Lamports; 12] = [
Lamports( 100_000), Lamports( 1_000_000), Lamports( 10_000_000), Lamports( 100_000_000), Lamports( 1_000_000_000), Lamports( 10_000_000_000), Lamports( 100_000_000_000), Lamports( 1_000_000_000_000), Lamports( 10_000_000_000_000), Lamports( 100_000_000_000_000), Lamports(1_000_000_000_000_000), Lamports(u64::MAX),
];
pub fn new() -> Self {
Self {
counts: [0; 12],
total: Lamports(0),
}
}
pub fn observe(&mut self, amount: Lamports) -> ProgramResult {
for (count, upper_bound) in self.counts.iter_mut().zip(&Self::BUCKET_UPPER_BOUNDS) {
if amount <= *upper_bound {
*count += 1;
}
}
self.total = (self.total + amount)?;
Ok(())
}
pub fn num_observations(&self) -> u64 {
self.counts[self.counts.len() - 1]
}
}
#[repr(C)]
#[derive(
Clone, Debug, Default, BorshDeserialize, BorshSerialize, BorshSchema, Eq, PartialEq, Serialize,
)]
pub struct WithdrawMetric {
pub total_st_sol_amount: StLamports,
pub total_sol_amount: Lamports,
pub count: u64,
}
impl WithdrawMetric {
fn observe(&mut self, st_sol_amount: StLamports, sol_amount: Lamports) -> token::Result<()> {
self.total_st_sol_amount = (self.total_st_sol_amount + st_sol_amount)?;
self.total_sol_amount = (self.total_sol_amount + sol_amount)?;
self.count += 1;
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_metrics_observe_fee_treasury() {
let mut m = Metrics::new();
m.observe_fee_treasury(Lamports(100), StLamports(100))
.unwrap();
m.observe_fee_treasury(Lamports(100), StLamports(80))
.unwrap();
assert_eq!(m.fee_treasury_sol_total, Lamports(200));
assert_eq!(m.fee_treasury_st_sol_total, StLamports(180));
}
#[test]
fn test_metrics_observe_fee_validation() {
let mut m = Metrics::new();
m.observe_fee_validation(Lamports(100), StLamports(100))
.unwrap();
m.observe_fee_validation(Lamports(100), StLamports(80))
.unwrap();
assert_eq!(m.fee_validation_sol_total, Lamports(200));
assert_eq!(m.fee_validation_st_sol_total, StLamports(180));
}
#[test]
fn test_metrics_observe_fee_developer() {
let mut m = Metrics::new();
m.observe_fee_developer(Lamports(100), StLamports(100))
.unwrap();
m.observe_fee_developer(Lamports(100), StLamports(80))
.unwrap();
assert_eq!(m.fee_developer_sol_total, Lamports(200));
assert_eq!(m.fee_developer_st_sol_total, StLamports(180));
}
#[test]
fn test_metrics_observe_reward_st_sol_appreciation() {
let mut m = Metrics::new();
m.observe_reward_st_sol_appreciation(Lamports(100)).unwrap();
m.observe_reward_st_sol_appreciation(Lamports(200)).unwrap();
assert_eq!(m.st_sol_appreciation_sol_total, Lamports(300));
}
#[test]
fn test_metrics_observe_deposit() {
let mut m = Metrics::new();
m.observe_deposit(Lamports(100)).unwrap();
m.observe_deposit(Lamports(1_000_000_000)).unwrap();
m.observe_deposit(Lamports(57_000_000_000)).unwrap();
m.observe_deposit(Lamports(21_000_000_000_000_000)).unwrap();
assert_eq!(m.deposit_amount.counts[0], 1);
assert_eq!(m.deposit_amount.counts[1], 1);
assert_eq!(m.deposit_amount.counts[2], 1);
assert_eq!(m.deposit_amount.counts[3], 1);
assert_eq!(m.deposit_amount.counts[4], 2);
assert_eq!(m.deposit_amount.counts[5], 2);
assert_eq!(m.deposit_amount.counts[6], 3);
assert_eq!(m.deposit_amount.counts[7], 3);
assert_eq!(m.deposit_amount.counts[8], 3);
assert_eq!(m.deposit_amount.counts[9], 3);
assert_eq!(m.deposit_amount.counts[10], 3);
assert_eq!(m.deposit_amount.counts[11], 4);
assert_eq!(m.deposit_amount.num_observations(), 4);
assert_eq!(m.deposit_amount.total, Lamports(21_000_058_000_000_100));
}
}