1use borsh::{BorshDeserialize, BorshSerialize};
2use solana_program::{account_info::AccountInfo, pubkey::Pubkey};
3use std::str::FromStr;
4use ttv_curve::math::{exchange_rate, logit, proportion_pt};
5use ttv_fixed_point::Number;
6
7#[derive(BorshSerialize, BorshDeserialize, Clone, Default)]
9pub struct MarketFinancials {
10 pub expiration_ts: u64,
12
13 pub pt_balance: u64,
16
17 pub sy_balance: u64,
20
21 pub ln_fee_rate_root: f64,
23
24 pub last_ln_implied_rate: f64,
27
28 pub rate_scalar_root: f64,
30}
31
32impl MarketFinancials {
33 pub const SIZE_OF: usize = 8 + 8 + 8 + 16 + 16 + 16;
34
35 fn asset_balance(&self, sy_exchange_rate: Number) -> Number {
36 Number::from_natural_u64(self.sy_balance) * sy_exchange_rate
37 }
38
39 fn current_rate_anchor(&self, sy_exchange_rate: Number, now: u64) -> f64 {
40 let sec_remaining = self.sec_remaining(now);
41 let asset = self.asset_balance(sy_exchange_rate).floor_u64();
42 let current_rate_scalar = self.current_rate_scalar(now);
43 ttv_curve::math::find_rate_anchor(
44 self.pt_balance,
45 asset,
46 current_rate_scalar,
47 self.last_ln_implied_rate.into(),
48 sec_remaining,
49 )
50 }
51
52 fn sec_remaining(&self, now: u64) -> u64 {
53 if now > self.expiration_ts {
54 0
55 } else {
56 self.expiration_ts - now
57 }
58 }
59
60 fn current_rate_scalar(&self, now: u64) -> f64 {
61 let sec_remaining = self.sec_remaining(now);
62 ttv_curve::math::rate_scalar::<f64>(self.rate_scalar_root, sec_remaining)
63 }
64}
65
66pub struct GetExchangeRateResult {
67 pub pt_asset_exchange_rate: f64,
68}
69
70pub fn get_exchange_rate(
71 market_account_info: &AccountInfo,
72 vault_account_info: &AccountInfo,
73 current_unix_timestamp: u64,
74) -> GetExchangeRateResult {
75 let program_id = Pubkey::from_str("ExponentnaRg3CQbW6dqQNZKXp7gtZ9DGMp1cwC4HAS7").unwrap();
76 assert_eq!(
77 market_account_info.owner, &program_id,
78 "Market account not owned by program"
79 );
80 assert_eq!(
81 vault_account_info.owner, &program_id,
82 "Vault account not owned by program"
83 );
84
85 let market_data = market_account_info.try_borrow_data().unwrap();
86 let vault_data = vault_account_info.try_borrow_data().unwrap();
87
88 assert_eq!(
89 &market_data[0..8],
90 &[212, 4, 132, 126, 169, 121, 121, 20],
91 "Invalid market account discriminator"
92 );
93 assert_eq!(
94 &vault_data[0..8],
95 &[211, 8, 232, 43, 2, 152, 117, 119],
96 "Invalid vault account discriminator"
97 );
98
99 let final_sy_exchange_rate = Number::from_bytes_le(&vault_data[401..401 + 32]);
100 let financials =
101 MarketFinancials::deserialize(&mut &market_data[364..364 + MarketFinancials::SIZE_OF])
102 .unwrap();
103
104 let asset_balance = financials.asset_balance(final_sy_exchange_rate);
105 let asset_balance_u64 = asset_balance.floor_u64();
106
107 let p = proportion_pt::<f64>(financials.pt_balance, asset_balance_u64);
108 let l_p = logit(p);
109
110 let current_rate_scalar = financials.current_rate_scalar(current_unix_timestamp);
111 let rate_anchor =
112 financials.current_rate_anchor(final_sy_exchange_rate, current_unix_timestamp);
113 let pt_asset_rate = exchange_rate(l_p, current_rate_scalar, rate_anchor);
114
115 GetExchangeRateResult {
116 pt_asset_exchange_rate: 1.00 / pt_asset_rate,
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 #[cfg(not(target_os = "solana"))]
124 use solana_client::rpc_client::RpcClient;
125
126 #[test]
127 #[cfg(not(target_os = "solana"))]
128 fn test_exchange_rates_with_real_data() {
129 let rpc_url = "https://api.mainnet-beta.solana.com".to_string();
131 let client = RpcClient::new(rpc_url);
132
133 let vault_address =
135 Pubkey::from_str("xzJdaENdahnFpPNZCTb36oRUtar3ZnRHMVB4ewDdRBe").unwrap();
136
137 let program_id = Pubkey::from_str("ExponentnaRg3CQbW6dqQNZKXp7gtZ9DGMp1cwC4HAS7").unwrap();
139 let (market_address, _) =
140 Pubkey::find_program_address(&[b"market", vault_address.as_ref()], &program_id);
141
142 let slot = client.get_slot().expect("Failed to get slot");
144 let current_time = client
145 .get_block_time(slot)
146 .expect("Failed to get block time") as u64;
147
148 let mut vault_account = client
150 .get_account(&vault_address)
151 .expect("Failed to get vault account");
152 let mut market_account = client
153 .get_account(&market_address)
154 .expect("Failed to get market account");
155
156 let mut vault_lamports = vault_account.lamports;
158 let mut market_lamports = market_account.lamports;
159
160 let vault_account_info = AccountInfo::new(
161 &vault_address,
162 false,
163 false,
164 &mut vault_lamports,
165 &mut vault_account.data,
166 &program_id,
167 false,
168 0,
169 );
170
171 let market_account_info = AccountInfo::new(
172 &market_address,
173 false,
174 false,
175 &mut market_lamports,
176 &mut market_account.data,
177 &program_id,
178 false,
179 0,
180 );
181
182 let result = get_exchange_rate(&market_account_info, &vault_account_info, current_time);
184
185 println!("PT -> Asset rate: {}", result.pt_asset_exchange_rate);
186 }
187}