solana_extra_wasm/program/spl_token_2022/extension/interest_bearing_mint/
mod.rs1use {
2 crate::program::spl_token_2022::{
3 extension::{Extension, ExtensionType},
4 pod::{OptionalNonZeroPubkey, PodI16, PodI64},
5 },
6 bytemuck::{Pod, Zeroable},
7 solana_sdk::program_error::ProgramError,
8 std::convert::TryInto,
9};
10
11pub mod instruction;
13
14pub mod processor;
16
17pub type BasisPoints = PodI16;
19const ONE_IN_BASIS_POINTS: f64 = 10_000.;
20const SECONDS_PER_YEAR: f64 = 60. * 60. * 24. * 365.24;
21
22pub type UnixTimestamp = PodI64;
24
25#[repr(C)]
34#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
35pub struct InterestBearingConfig {
36 pub rate_authority: OptionalNonZeroPubkey,
38 pub initialization_timestamp: UnixTimestamp,
40 pub pre_update_average_rate: BasisPoints,
42 pub last_update_timestamp: UnixTimestamp,
44 pub current_rate: BasisPoints,
46}
47impl InterestBearingConfig {
48 fn pre_update_timespan(&self) -> Option<i64> {
49 i64::from(self.last_update_timestamp).checked_sub(self.initialization_timestamp.into())
50 }
51
52 fn pre_update_exp(&self) -> Option<f64> {
53 let numerator = (i16::from(self.pre_update_average_rate) as i128)
54 .checked_mul(self.pre_update_timespan()? as i128)? as f64;
55 let exponent = numerator / SECONDS_PER_YEAR / ONE_IN_BASIS_POINTS;
56 Some(exponent.exp())
57 }
58
59 fn post_update_timespan(&self, unix_timestamp: i64) -> Option<i64> {
60 unix_timestamp.checked_sub(self.last_update_timestamp.into())
61 }
62
63 fn post_update_exp(&self, unix_timestamp: i64) -> Option<f64> {
64 let numerator = (i16::from(self.current_rate) as i128)
65 .checked_mul(self.post_update_timespan(unix_timestamp)? as i128)?
66 as f64;
67 let exponent = numerator / SECONDS_PER_YEAR / ONE_IN_BASIS_POINTS;
68 Some(exponent.exp())
69 }
70
71 fn total_scale(&self, decimals: u8, unix_timestamp: i64) -> Option<f64> {
72 Some(
73 self.pre_update_exp()? * self.post_update_exp(unix_timestamp)?
74 / 10_f64.powi(decimals as i32),
75 )
76 }
77
78 pub fn amount_to_ui_amount(
81 &self,
82 amount: u64,
83 decimals: u8,
84 unix_timestamp: i64,
85 ) -> Option<String> {
86 let scaled_amount_with_interest =
87 (amount as f64) * self.total_scale(decimals, unix_timestamp)?;
88 Some(scaled_amount_with_interest.to_string())
89 }
90
91 pub fn try_ui_amount_into_amount(
94 &self,
95 ui_amount: &str,
96 decimals: u8,
97 unix_timestamp: i64,
98 ) -> Result<u64, ProgramError> {
99 let scaled_amount = ui_amount
100 .parse::<f64>()
101 .map_err(|_| ProgramError::InvalidArgument)?;
102 let amount = scaled_amount
103 / self
104 .total_scale(decimals, unix_timestamp)
105 .ok_or(ProgramError::InvalidArgument)?;
106 if amount > (u64::MAX as f64) || amount < (u64::MIN as f64) || amount.is_nan() {
107 Err(ProgramError::InvalidArgument)
108 } else {
109 Ok(amount.round() as u64) }
111 }
112
113 pub fn time_weighted_average_rate(&self, current_timestamp: i64) -> Option<i16> {
122 let initialization_timestamp = i64::from(self.initialization_timestamp) as i128;
123 let last_update_timestamp = i64::from(self.last_update_timestamp) as i128;
124
125 let r_1 = i16::from(self.pre_update_average_rate) as i128;
126 let t_1 = last_update_timestamp.checked_sub(initialization_timestamp)?;
127 let r_2 = i16::from(self.current_rate) as i128;
128 let t_2 = (current_timestamp as i128).checked_sub(last_update_timestamp)?;
129 let total_timespan = t_1.checked_add(t_2)?;
130 let average_rate = if total_timespan == 0 {
131 r_2
134 } else {
135 r_1.checked_mul(t_1)?
136 .checked_add(r_2.checked_mul(t_2)?)?
137 .checked_div(total_timespan)?
138 };
139 average_rate.try_into().ok()
140 }
141}
142impl Extension for InterestBearingConfig {
143 const TYPE: ExtensionType = ExtensionType::InterestBearingConfig;
144}