stable_swap_client/
state.rs

1//! State transition types
2
3use crate::fees::Fees;
4use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
5use solana_program::{
6    program_error::ProgramError,
7    program_pack::{IsInitialized, Pack, Sealed},
8    pubkey::Pubkey,
9};
10
11/// Program states.
12#[repr(C)]
13#[derive(Clone, Copy, Debug, PartialEq)]
14pub struct SwapInfo {
15    /// Initialized state
16    pub is_initialized: bool,
17
18    /// Paused state
19    pub is_paused: bool,
20
21    /// Nonce used in program address
22    /// The program address is created deterministically with the nonce,
23    /// swap program id, and swap account pubkey.  This program address has
24    /// authority over the swap's token A account, token B account, and pool
25    /// token mint.
26    pub nonce: u8,
27
28    /// Initial amplification coefficient (A)
29    pub initial_amp_factor: u64,
30    /// Target amplification coefficient (A)
31    pub target_amp_factor: u64,
32    /// Ramp A start timestamp
33    pub start_ramp_ts: i64,
34    /// Ramp A stop timestamp
35    pub stop_ramp_ts: i64,
36
37    /// Deadline to transfer admin control to future_admin_key
38    pub future_admin_deadline: i64,
39    /// Public key of the admin account to be applied
40    pub future_admin_key: Pubkey,
41    /// Public key of admin account to execute admin instructions
42    pub admin_key: Pubkey,
43
44    /// Token A
45    pub token_a: SwapTokenInfo,
46    /// Token B
47    pub token_b: SwapTokenInfo,
48
49    /// Pool tokens are issued when A or B tokens are deposited.
50    /// Pool tokens can be withdrawn back to the original A or B token.
51    pub pool_mint: Pubkey,
52    /// Fees
53    pub fees: Fees,
54}
55
56/// Information about one of the tokens.
57#[repr(C)]
58#[derive(Clone, Copy, Debug, PartialEq)]
59pub struct SwapTokenInfo {
60    /// Token account for pool reserves
61    pub reserves: Pubkey,
62    /// Mint information for the token
63    pub mint: Pubkey,
64    /// Public key of the admin token account to receive trading and / or withdrawal fees for token
65    pub admin_fees: Pubkey,
66    /// The index of the token. Token A = 0, Token B = 1.
67    pub index: u8,
68}
69
70impl Sealed for SwapInfo {}
71impl IsInitialized for SwapInfo {
72    fn is_initialized(&self) -> bool {
73        self.is_initialized
74    }
75}
76
77impl Pack for SwapInfo {
78    const LEN: usize = 395;
79
80    /// Unpacks a byte buffer into a [SwapInfo](struct.SwapInfo.html).
81    fn unpack_from_slice(input: &[u8]) -> Result<Self, ProgramError> {
82        let input = array_ref![input, 0, 395];
83        #[allow(clippy::ptr_offset_with_cast)]
84        let (
85            is_initialized,
86            is_paused,
87            nonce,
88            initial_amp_factor,
89            target_amp_factor,
90            start_ramp_ts,
91            stop_ramp_ts,
92            future_admin_deadline,
93            future_admin_key,
94            admin_key,
95            token_a,
96            token_b,
97            pool_mint,
98            token_a_mint,
99            token_b_mint,
100            admin_fee_key_a,
101            admin_fee_key_b,
102            fees,
103        ) = array_refs![input, 1, 1, 1, 8, 8, 8, 8, 8, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64];
104        Ok(Self {
105            is_initialized: match is_initialized {
106                [0] => false,
107                [1] => true,
108                _ => return Err(ProgramError::InvalidAccountData),
109            },
110            is_paused: match is_paused {
111                [0] => false,
112                [1] => true,
113                _ => return Err(ProgramError::InvalidAccountData),
114            },
115            nonce: nonce[0],
116            initial_amp_factor: u64::from_le_bytes(*initial_amp_factor),
117            target_amp_factor: u64::from_le_bytes(*target_amp_factor),
118            start_ramp_ts: i64::from_le_bytes(*start_ramp_ts),
119            stop_ramp_ts: i64::from_le_bytes(*stop_ramp_ts),
120            future_admin_deadline: i64::from_le_bytes(*future_admin_deadline),
121            future_admin_key: Pubkey::new_from_array(*future_admin_key),
122            admin_key: Pubkey::new_from_array(*admin_key),
123            token_a: SwapTokenInfo {
124                reserves: Pubkey::new_from_array(*token_a),
125                mint: Pubkey::new_from_array(*token_a_mint),
126                admin_fees: Pubkey::new_from_array(*admin_fee_key_a),
127                index: 0,
128            },
129            token_b: SwapTokenInfo {
130                reserves: Pubkey::new_from_array(*token_b),
131                mint: Pubkey::new_from_array(*token_b_mint),
132                admin_fees: Pubkey::new_from_array(*admin_fee_key_b),
133                index: 1,
134            },
135            pool_mint: Pubkey::new_from_array(*pool_mint),
136            fees: Fees::unpack_from_slice(fees)?,
137        })
138    }
139
140    fn pack_into_slice(&self, output: &mut [u8]) {
141        let output = array_mut_ref![output, 0, 395];
142        let (
143            is_initialized,
144            is_paused,
145            nonce,
146            initial_amp_factor,
147            target_amp_factor,
148            start_ramp_ts,
149            stop_ramp_ts,
150            future_admin_deadline,
151            future_admin_key,
152            admin_key,
153            token_a,
154            token_b,
155            pool_mint,
156            token_a_mint,
157            token_b_mint,
158            admin_fee_key_a,
159            admin_fee_key_b,
160            fees,
161        ) = mut_array_refs![output, 1, 1, 1, 8, 8, 8, 8, 8, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64];
162        is_initialized[0] = self.is_initialized as u8;
163        is_paused[0] = self.is_paused as u8;
164        nonce[0] = self.nonce;
165        *initial_amp_factor = self.initial_amp_factor.to_le_bytes();
166        *target_amp_factor = self.target_amp_factor.to_le_bytes();
167        *start_ramp_ts = self.start_ramp_ts.to_le_bytes();
168        *stop_ramp_ts = self.stop_ramp_ts.to_le_bytes();
169        *future_admin_deadline = self.future_admin_deadline.to_le_bytes();
170        future_admin_key.copy_from_slice(self.future_admin_key.as_ref());
171        admin_key.copy_from_slice(self.admin_key.as_ref());
172        token_a.copy_from_slice(self.token_a.reserves.as_ref());
173        token_b.copy_from_slice(self.token_b.reserves.as_ref());
174        pool_mint.copy_from_slice(self.pool_mint.as_ref());
175        token_a_mint.copy_from_slice(self.token_a.mint.as_ref());
176        token_b_mint.copy_from_slice(self.token_b.mint.as_ref());
177        admin_fee_key_a.copy_from_slice(self.token_a.admin_fees.as_ref());
178        admin_fee_key_b.copy_from_slice(self.token_b.admin_fees.as_ref());
179        self.fees.pack_into_slice(&mut fees[..]);
180    }
181}
182
183#[cfg(test)]
184#[allow(clippy::unwrap_used)]
185mod tests {
186    use super::*;
187
188    #[test]
189    fn test_swap_info_packing() {
190        let nonce = 255;
191        let initial_amp_factor: u64 = 1;
192        let target_amp_factor: u64 = 1;
193        let start_ramp_ts: i64 = i64::MAX;
194        let stop_ramp_ts: i64 = i64::MAX;
195        let future_admin_deadline: i64 = i64::MAX;
196        let future_admin_key_raw = [1u8; 32];
197        let admin_key_raw = [2u8; 32];
198        let token_a_raw = [3u8; 32];
199        let token_b_raw = [4u8; 32];
200        let pool_mint_raw = [5u8; 32];
201        let token_a_mint_raw = [6u8; 32];
202        let token_b_mint_raw = [7u8; 32];
203        let admin_fee_key_a_raw = [8u8; 32];
204        let admin_fee_key_b_raw = [9u8; 32];
205        let admin_key = Pubkey::new_from_array(admin_key_raw);
206        let future_admin_key = Pubkey::new_from_array(future_admin_key_raw);
207        let token_a = Pubkey::new_from_array(token_a_raw);
208        let token_b = Pubkey::new_from_array(token_b_raw);
209        let pool_mint = Pubkey::new_from_array(pool_mint_raw);
210        let token_a_mint = Pubkey::new_from_array(token_a_mint_raw);
211        let token_b_mint = Pubkey::new_from_array(token_b_mint_raw);
212        let admin_fee_key_a = Pubkey::new_from_array(admin_fee_key_a_raw);
213        let admin_fee_key_b = Pubkey::new_from_array(admin_fee_key_b_raw);
214        let admin_trade_fee_numerator = 1;
215        let admin_trade_fee_denominator = 2;
216        let admin_withdraw_fee_numerator = 3;
217        let admin_withdraw_fee_denominator = 4;
218        let trade_fee_numerator = 5;
219        let trade_fee_denominator = 6;
220        let withdraw_fee_numerator = 7;
221        let withdraw_fee_denominator = 8;
222        let fees = Fees {
223            admin_trade_fee_numerator,
224            admin_trade_fee_denominator,
225            admin_withdraw_fee_numerator,
226            admin_withdraw_fee_denominator,
227            trade_fee_numerator,
228            trade_fee_denominator,
229            withdraw_fee_numerator,
230            withdraw_fee_denominator,
231        };
232
233        let is_initialized = true;
234        let is_paused = false;
235        let swap_info = SwapInfo {
236            is_initialized,
237            is_paused,
238            nonce,
239            initial_amp_factor,
240            target_amp_factor,
241            start_ramp_ts,
242            stop_ramp_ts,
243            future_admin_deadline,
244            future_admin_key,
245            admin_key,
246            token_a: SwapTokenInfo {
247                reserves: token_a,
248                mint: token_a_mint,
249                admin_fees: admin_fee_key_a,
250                index: 0,
251            },
252            token_b: SwapTokenInfo {
253                reserves: token_b,
254                mint: token_b_mint,
255                admin_fees: admin_fee_key_b,
256                index: 1,
257            },
258            pool_mint,
259            fees,
260        };
261
262        let mut packed = [0u8; SwapInfo::LEN];
263        SwapInfo::pack(swap_info, &mut packed).unwrap();
264        let unpacked = SwapInfo::unpack(&packed).unwrap();
265        assert_eq!(swap_info, unpacked);
266
267        let mut packed = vec![
268            1_u8, // is_initialized
269            0_u8, // is_paused
270            nonce,
271        ];
272        packed.extend_from_slice(&initial_amp_factor.to_le_bytes());
273        packed.extend_from_slice(&target_amp_factor.to_le_bytes());
274        packed.extend_from_slice(&start_ramp_ts.to_le_bytes());
275        packed.extend_from_slice(&stop_ramp_ts.to_le_bytes());
276        packed.extend_from_slice(&future_admin_deadline.to_le_bytes());
277        packed.extend_from_slice(&future_admin_key_raw);
278        packed.extend_from_slice(&admin_key_raw);
279        packed.extend_from_slice(&token_a_raw);
280        packed.extend_from_slice(&token_b_raw);
281        packed.extend_from_slice(&pool_mint_raw);
282        packed.extend_from_slice(&token_a_mint_raw);
283        packed.extend_from_slice(&token_b_mint_raw);
284        packed.extend_from_slice(&admin_fee_key_a_raw);
285        packed.extend_from_slice(&admin_fee_key_b_raw);
286        packed.extend_from_slice(&admin_trade_fee_numerator.to_le_bytes());
287        packed.extend_from_slice(&admin_trade_fee_denominator.to_le_bytes());
288        packed.extend_from_slice(&admin_withdraw_fee_numerator.to_le_bytes());
289        packed.extend_from_slice(&admin_withdraw_fee_denominator.to_le_bytes());
290        packed.extend_from_slice(&trade_fee_numerator.to_le_bytes());
291        packed.extend_from_slice(&trade_fee_denominator.to_le_bytes());
292        packed.extend_from_slice(&withdraw_fee_numerator.to_le_bytes());
293        packed.extend_from_slice(&withdraw_fee_denominator.to_le_bytes());
294        let unpacked = SwapInfo::unpack(&packed).unwrap();
295        assert_eq!(swap_info, unpacked);
296    }
297}