exponent_amm_exchange_rate/
lib.rs

1use borsh::{BorshDeserialize, BorshSerialize};
2use solana_program::{account_info::AccountInfo, pubkey::Pubkey};
3use std::str::FromStr;
4
5/// Financial parameters for the market
6#[derive(BorshSerialize, BorshDeserialize, Clone, Default)]
7pub struct MarketFinancials {
8    /// Expiration timestamp, which is copied from the vault associated with the PT
9    pub expiration_ts: u64,
10
11    /// Balance of PT in the market
12    /// This amount is tracked separately to prevent bugs from token transfers directly to the market
13    pub pt_balance: u64,
14
15    /// Balance of SY in the market
16    /// This amount is tracked separately to prevent bugs from token transfers directly to the market
17    pub sy_balance: u64,
18
19    /// Initial log of fee rate, which decreases over time
20    pub ln_fee_rate_root: f64,
21
22    /// Last seen log of implied rate (APY) for PT
23    /// Used to maintain continuity of the APY between trades over time
24    pub last_ln_implied_rate: f64,
25
26    /// Initial rate scalar, which increases over time
27    pub rate_scalar_root: f64,
28}
29
30impl MarketFinancials {
31    pub const SIZE_OF: usize = 8 + 8 + 8 + 16 + 16 + 16;
32
33    pub fn exchange_rate(&self, unix_timestamp: u64) -> f64 {
34        exponent_time_curve::math::exchange_rate_from_ln_implied_rate::<f64>(
35            self.last_ln_implied_rate.into(),
36            self.sec_remaining(unix_timestamp),
37        )
38    }
39
40    fn sec_remaining(&self, now: u64) -> u64 {
41        if now > self.expiration_ts {
42            0
43        } else {
44            self.expiration_ts - now
45        }
46    }
47}
48
49pub struct GetExchangeRateResult {
50    pub pt_asset_exchange_rate: f64,
51}
52
53pub fn get_exchange_rate(
54    market_account_info: &AccountInfo,
55    vault_account_info: &AccountInfo,
56    current_unix_timestamp: u64,
57) -> GetExchangeRateResult {
58    let program_id = Pubkey::from_str("ExponentnaRg3CQbW6dqQNZKXp7gtZ9DGMp1cwC4HAS7").unwrap();
59    assert_eq!(
60        market_account_info.owner, &program_id,
61        "Market account not owned by program"
62    );
63    assert_eq!(
64        vault_account_info.owner, &program_id,
65        "Vault account not owned by program"
66    );
67
68    let market_data = market_account_info.try_borrow_data().unwrap();
69    let vault_data = vault_account_info.try_borrow_data().unwrap();
70
71    assert_eq!(
72        &market_data[0..8],
73        &[212, 4, 132, 126, 169, 121, 121, 20],
74        "Invalid market account discriminator"
75    );
76    assert_eq!(
77        &vault_data[0..8],
78        &[211, 8, 232, 43, 2, 152, 117, 119],
79        "Invalid vault account discriminator"
80    );
81
82    let financials =
83        MarketFinancials::deserialize(&mut &market_data[364..364 + MarketFinancials::SIZE_OF])
84            .unwrap();
85
86    let exchange_rate_from_ln_implied_rate = financials.exchange_rate(current_unix_timestamp);
87
88    GetExchangeRateResult {
89        pt_asset_exchange_rate: 1.00 / exchange_rate_from_ln_implied_rate,
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    #[cfg(not(target_os = "solana"))]
97    use solana_client::rpc_client::RpcClient;
98
99    #[test]
100    #[cfg(not(target_os = "solana"))]
101    fn test_exchange_rates_with_real_data() {
102        // Initialize RPC client
103        let rpc_url = "https://api.mainnet-beta.solana.com".to_string();
104        let client = RpcClient::new(rpc_url);
105
106        // Example vault address - replace with the vault address you want to test with
107        let vault_address =
108            Pubkey::from_str("9YbaicMsXrtupkpD72pdWBfU6R7EJfSByw75sEpDM1uH").unwrap();
109
110        // Derive market address
111        let program_id = Pubkey::from_str("ExponentnaRg3CQbW6dqQNZKXp7gtZ9DGMp1cwC4HAS7").unwrap();
112        let (market_address, _) =
113            Pubkey::find_program_address(&[b"market", vault_address.as_ref()], &program_id);
114
115        // Get current Solana timestamp
116        let slot = client.get_slot().expect("Failed to get slot");
117        let current_time = client
118            .get_block_time(slot)
119            .expect("Failed to get block time") as u64;
120
121        // Fetch accounts
122        let mut vault_account = client
123            .get_account(&vault_address)
124            .expect("Failed to get vault account");
125        let mut market_account = client
126            .get_account(&market_address)
127            .expect("Failed to get market account");
128
129        // Create AccountInfo structs
130        let mut vault_lamports = vault_account.lamports;
131        let mut market_lamports = market_account.lamports;
132
133        let vault_account_info = AccountInfo::new(
134            &vault_address,
135            false,
136            false,
137            &mut vault_lamports,
138            &mut vault_account.data,
139            &program_id,
140            false,
141            0,
142        );
143
144        let market_account_info = AccountInfo::new(
145            &market_address,
146            false,
147            false,
148            &mut market_lamports,
149            &mut market_account.data,
150            &program_id,
151            false,
152            0,
153        );
154
155        // Get exchange rates
156        let result = get_exchange_rate(&market_account_info, &vault_account_info, current_time);
157
158        println!("PT -> Asset rate: {}", result.pt_asset_exchange_rate);
159    }
160}