1use solana_program::program_error::ProgramError;
2use solana_program::program_error::ProgramError::InvalidInstructionData;
3
4#[derive(Debug, PartialEq)]
5pub enum SwapInstruction {
6 CreatePool {
18 seed: [u8; 32],
19 lp_fee_rate: u32,
20 creator_fee_rate: u32,
21 },
22
23 Swap {
34 in_amount: u64,
35 min_out_amount: u64,
36 },
37
38 Deposit {
50 min_a: u64,
52 max_a: u64,
53 min_b: u64,
54 max_b: u64,
55 },
56
57 Withdraw {
69 lp_amount: u64,
70 min_a: u64,
71 min_b: u64,
72 },
73
74 }
77
78impl SwapInstruction {
80 const MODULE_TAG: u8 = 1;
81
82 pub fn pack(&self) -> Vec<u8> {
83 let mut buffer = Vec::new();
84 buffer.push(SwapInstruction::MODULE_TAG);
85
86 match self {
87 SwapInstruction::CreatePool { seed, lp_fee_rate, creator_fee_rate } => {
88 buffer.push(0);
89 buffer.extend_from_slice(seed);
90 buffer.extend_from_slice(&lp_fee_rate.to_le_bytes());
91 buffer.extend_from_slice(&creator_fee_rate.to_le_bytes());
92 }
93 SwapInstruction::Swap { in_amount, min_out_amount } => {
94 buffer.push(1);
95 buffer.extend_from_slice(&in_amount.to_le_bytes());
96 buffer.extend_from_slice(&min_out_amount.to_le_bytes());
97 }
98 SwapInstruction::Deposit { min_a, max_a, min_b, max_b } => {
99 buffer.push(2);
100 buffer.extend_from_slice(&min_a.to_le_bytes());
101 buffer.extend_from_slice(&max_a.to_le_bytes());
102 buffer.extend_from_slice(&min_b.to_le_bytes());
103 buffer.extend_from_slice(&max_b.to_le_bytes());
104 }
105 SwapInstruction::Withdraw { lp_amount, min_a, min_b } => {
106 buffer.push(3);
107 buffer.extend_from_slice(&lp_amount.to_le_bytes());
108 buffer.extend_from_slice(&min_a.to_le_bytes());
109 buffer.extend_from_slice(&min_b.to_le_bytes())
110 }
111 };
112
113 buffer
114 }
115
116 pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
117 let (module_tag, rest) = input.split_first().ok_or(InvalidInstructionData)?;
118 if *module_tag != SwapInstruction::MODULE_TAG {
119 return Err(InvalidInstructionData);
120 }
121
122 let (tag, rest) = rest.split_first().ok_or(InvalidInstructionData)?;
123
124 match tag {
125 0 => {
126 let seed = rest
127 .get(..32)
128 .and_then(|slice| slice.try_into().ok())
129 .ok_or(InvalidInstructionData)?;
130
131 let lp_fee_rate = rest.get(32..36)
132 .and_then(|slice| slice.try_into().ok())
133 .map(u32::from_le_bytes)
134 .ok_or(InvalidInstructionData)?;
135
136 let creator_fee_rate = rest.get(36..40)
137 .and_then(|slice| slice.try_into().ok())
138 .map(u32::from_le_bytes)
139 .ok_or(InvalidInstructionData)?;
140
141 Ok(SwapInstruction::CreatePool { seed, lp_fee_rate, creator_fee_rate })
142 }
143 1 => {
144 let in_amount = rest.get(..8)
145 .and_then(|slice| slice.try_into().ok())
146 .map(u64::from_le_bytes)
147 .ok_or(InvalidInstructionData)?;
148
149 let min_out_amount = rest.get(8..16)
150 .and_then(|slice| slice.try_into().ok())
151 .map(u64::from_le_bytes)
152 .ok_or(InvalidInstructionData)?;
153
154 Ok(SwapInstruction::Swap { in_amount, min_out_amount })
155 }
156 2 => {
157 let min_a = rest.get(..8)
158 .and_then(|slice| slice.try_into().ok())
159 .map(u64::from_le_bytes)
160 .ok_or(InvalidInstructionData)?;
161
162 let max_a = rest.get(8..16)
163 .and_then(|slice| slice.try_into().ok())
164 .map(u64::from_le_bytes)
165 .ok_or(InvalidInstructionData)?;
166
167 let min_b = rest.get(16..24)
168 .and_then(|slice| slice.try_into().ok())
169 .map(u64::from_le_bytes)
170 .ok_or(InvalidInstructionData)?;
171
172 let max_b = rest.get(24..32)
173 .and_then(|slice| slice.try_into().ok())
174 .map(u64::from_le_bytes)
175 .ok_or(InvalidInstructionData)?;
176
177 Ok(SwapInstruction::Deposit { min_a, max_a, min_b, max_b })
178 }
179 3 => {
180 let lp_amount = rest.get(..8)
181 .and_then(|slice| slice.try_into().ok())
182 .map(u64::from_le_bytes)
183 .ok_or(InvalidInstructionData)?;
184
185 let min_a = rest.get(8..16)
186 .and_then(|slice| slice.try_into().ok())
187 .map(u64::from_le_bytes)
188 .ok_or(InvalidInstructionData)?;
189
190 let min_b = rest.get(16..24)
191 .and_then(|slice| slice.try_into().ok())
192 .map(u64::from_le_bytes)
193 .ok_or(InvalidInstructionData)?;
194
195 Ok(SwapInstruction::Withdraw { lp_amount, min_a, min_b })
196 }
197 _ => Err(InvalidInstructionData)
198 }
199 }
200}
201
202
203pub fn calculate_deposit_amounts(pool_a_amount: u64, pool_b_amount: u64, lp_supply: u64,
204 deposit_max_a: u64, deposit_max_b: u64) -> Option<(u64, u64, u64)> {
205 if lp_supply == 0 {
206 return Some((deposit_max_a, deposit_max_b, 10_000_000_000 as u64));
208 }
209
210 let pool_ratio = (pool_a_amount as u128)
211 .checked_mul(u64::MAX as u128)?
212 .checked_div(pool_b_amount as u128)?;
213
214 let deposit_ratio = (deposit_max_a as u128)
215 .checked_mul(u64::MAX as u128)?
216 .checked_div(deposit_max_b as u128)?;
217
218 let (deposit_a, deposit_b) = if deposit_ratio >= pool_ratio {
219 let deposit_a: u64 = (deposit_max_b as u128)
220 .checked_mul(pool_ratio)?
221 .checked_div(u64::MAX as u128)?
222 .try_into().ok()?;
223
224 (deposit_a, deposit_max_b)
225 } else {
226 let deposit_b: u64 = (deposit_max_a as u128)
227 .checked_mul(u64::MAX as u128)?
228 .checked_div(pool_ratio)?
229 .try_into().ok()?;
230
231 (deposit_max_a, deposit_b)
232 };
233
234 let lp_mint_amount = (deposit_a as u128)
235 .checked_mul(u64::MAX as u128)?
236 .checked_div(pool_a_amount as u128)?
237 .checked_mul(lp_supply as u128)?
238 .checked_div(u64::MAX as u128)?
239 .try_into().ok()?;
240
241 Some((deposit_a, deposit_b, lp_mint_amount))
242}
243
244const FEE_RATE_BASE_DIVIDER: u128 = 100_000_000;
245
246fn calculate_fee_amount(amount: u128, fee_rate: u32) -> Option<u128> {
247 Some(if fee_rate == 0 {
248 0
249 } else {
250 amount
251 .checked_mul(fee_rate as u128)?
252 .checked_div(FEE_RATE_BASE_DIVIDER)?
253 })
254}
255
256pub fn calculate_swap_amounts(pool_balance_in_token: u64, pool_balance_out_token: u64, swap_in_amount: u64,
257 dao_fee_rate: u32, lp_fee_rate: u32, creator_fee_rate: u32) -> Option<(u64, u64, u64, u64)> {
258 let swap_in_amount = swap_in_amount as u128;
259
260 let dao_fee_amount = calculate_fee_amount(swap_in_amount, dao_fee_rate)?;
261 let lp_fee_amount = calculate_fee_amount(swap_in_amount, lp_fee_rate)?;
262 let creator_fee_amount = calculate_fee_amount(swap_in_amount, creator_fee_rate)?;
263
264 let pool_balance_in_token_after_fees = (pool_balance_in_token as u128)
265 .checked_add(lp_fee_amount)?;
266 let swap_in_amount_after_fees = swap_in_amount
267 .checked_sub(dao_fee_amount)?
268 .checked_sub(lp_fee_amount)?
269 .checked_sub(creator_fee_amount)?;
270
271 let swap_out_amount = (pool_balance_out_token as u128)
275 .checked_mul(swap_in_amount_after_fees)?
276 .checked_div(
277 pool_balance_in_token_after_fees
278 .checked_add(swap_in_amount_after_fees)?
279 )?;
280
281 Some((
282 swap_out_amount.try_into().ok()?,
283 dao_fee_amount.try_into().ok()?,
284 lp_fee_amount.try_into().ok()?,
285 creator_fee_amount.try_into().ok()?
286 ))
287}
288
289pub fn calculate_withdraw_amounts(pool_a_amount: u64, pool_b_amount: u64, lp_supply: u64,
290 withdraw_lp_amount: u64) -> Option<(u64, u64)> {
291 if withdraw_lp_amount == lp_supply {
292 return Some((pool_a_amount, pool_b_amount));
293 }
294
295 let withdraw_ratio = (withdraw_lp_amount as u128)
296 .checked_mul(u64::MAX as u128)?
297 .checked_div(lp_supply as u128)?;
298
299 let withdraw_a_amount = (pool_a_amount as u128)
300 .checked_mul(withdraw_ratio)?
301 .checked_div(u64::MAX as u128)?
302 .try_into().ok()?;
303
304 let withdraw_b_amount = (pool_b_amount as u128)
305 .checked_mul(withdraw_ratio)?
306 .checked_div(u64::MAX as u128)?
307 .try_into().ok()?;
308
309 Some((withdraw_a_amount, withdraw_b_amount))
310}
311
312
313#[cfg(test)]
314mod tests {
315 use solana_program::pubkey::Pubkey;
316 use super::*;
317
318 #[test]
319 fn test_pack_unpack_swap_instruction() {
320 let create_instruction = SwapInstruction::CreatePool {
321 seed: Pubkey::new_unique().to_bytes(),
322 lp_fee_rate: 5,
323 creator_fee_rate: 60,
324 };
325 assert_eq!(create_instruction, SwapInstruction::unpack(&create_instruction.pack()).unwrap());
326 assert_ne!(create_instruction, SwapInstruction::unpack(&SwapInstruction::CreatePool {
327 seed: Default::default(),
328 lp_fee_rate: 0,
329 creator_fee_rate: 0,
330 }.pack()).unwrap());
331
332
333 let swap_instruction = SwapInstruction::Swap { in_amount: 1, min_out_amount: 2 };
334 assert_eq!(swap_instruction, SwapInstruction::unpack(&swap_instruction.pack()).unwrap());
335 assert_ne!(swap_instruction, SwapInstruction::unpack(&SwapInstruction::Swap {
336 in_amount: 0,
337 min_out_amount: 0,
338 }.pack()).unwrap());
339
340 let deposit_instruction = SwapInstruction::Deposit {
341 min_a: 1,
342 max_a: 2,
343 min_b: 3,
344 max_b: 4,
345 };
346
347 assert_eq!(deposit_instruction, SwapInstruction::unpack(&deposit_instruction.pack()).unwrap());
348 assert_ne!(deposit_instruction, SwapInstruction::unpack(&SwapInstruction::Deposit {
349 min_a: 1,
350 max_a: 1,
351 min_b: 1,
352 max_b: 1,
353 }.pack()).unwrap());
354
355
356 let withdraw_instruction = SwapInstruction::Withdraw {
357 lp_amount: 1,
358 min_a: 2,
359 min_b: 3,
360 };
361 assert_eq!(withdraw_instruction, SwapInstruction::unpack(&withdraw_instruction.pack()).unwrap());
362 assert_ne!(withdraw_instruction, SwapInstruction::unpack(&SwapInstruction::Withdraw {
363 lp_amount: 1,
364 min_a: 1,
365 min_b: 1,
366 }.pack()).unwrap());
367 }
368
369
370 #[test]
371 fn test_calculate_deposit_amounts() {
372 assert_eq!(
373 Some((69, 420, 10_000_000_000)),
374 calculate_deposit_amounts(0, 0, 0, 69, 420)
375 );
376
377 assert_eq!(
378 Some((100, 100, 10_000)),
379 calculate_deposit_amounts(100, 100, 10_000, 100, 100)
380 );
381 assert_eq!(
382 Some((100, 100, 10_000)),
383 calculate_deposit_amounts(100, 100, 10_000, 110, 100)
384 );
385 assert_eq!(
386 Some((100, 100, 10_000)),
387 calculate_deposit_amounts(100, 100, 10_000, 100, 110)
388 );
389
390
391 }
397
398 #[test]
399 fn test_calculate_swap_amounts_with_fees() {
400 assert_eq!(
402 Some((88_342, 1_000, 1_000, 1_000)),
403 calculate_swap_amounts(1_000_000, 1_000_000, 100_000,
404 1_000_000, 1_000_000, 1_000_000)
405 );
406
407 assert_eq!(
409 Some((8920, 90000, 990, 0)),
410 calculate_swap_amounts(1_000_000, 1_000_000, 100_000,
411 90_000_000, 990_000, 0)
412 );
413
414 assert_eq!(
416 Some((8198, 990, 90000, 0)),
417 calculate_swap_amounts(1_000_000, 1_000_000, 100_000,
418 990_000, 90_000_000, 0)
419 );
420
421 assert_eq!(
423 None,
424 calculate_swap_amounts(1_000_000, 1_000_000, 100_000,
425 50_000_000, 50_000_000, 1_000_000)
426 );
427
428 }
430
431 #[test]
432 fn test_calculate_swap_amounts_without_fees() {
433 assert_eq!(Some((0, 0, 0, 0)), calculate_swap_amounts(1, 100, 0, 0, 0, 0));
434 assert_eq!(Some((0, 0, 0, 0)), calculate_swap_amounts(100, 10, 11, 0, 0, 0));
435 assert_eq!(Some((1, 0, 0, 0)), calculate_swap_amounts(100_000_000, 100, 1_011_000, 0, 0, 0));
436 assert_eq!(Some((4, 0, 0, 0)), calculate_swap_amounts(100, 100, 5, 0, 0, 0));
437 assert_eq!(Some((49_950_049, 0, 0, 0)), calculate_swap_amounts(100_000_000_000, 50_000_000_000, 100_000_000, 0, 0, 0));
438 assert_eq!(Some((372_208_436, 0, 0, 0)), calculate_swap_amounts(100_000_000_000, 50_000_000_000, 750_000_000, 0, 0, 0));
439 assert_eq!(Some((3_333_333, 0, 0, 0)), calculate_swap_amounts(10_000_000, 10_000_000, 5_000_000, 0, 0, 0));
440 assert_eq!(Some((6_666_666, 0, 0, 0)), calculate_swap_amounts(10_000_000, 10_000_000, 20_000_000, 0, 0, 0));
441 assert_eq!(Some((8_000_000, 0, 0, 0)), calculate_swap_amounts(10_000_000, 10_000_000, 40_000_000, 0, 0, 0));
442 assert_eq!(Some((12_990_906, 0, 0, 0)), calculate_swap_amounts(70_000_000, 13_000_000, 100_000_000_000, 0, 0, 0));
443 }
444
445 #[test]
446 fn test_calculate_withdraw_amounts() {
447 assert_eq!(
448 Some((10, 10)),
449 calculate_withdraw_amounts(10, 10, 100, 100)
450 );
451
452 assert_eq!(
453 Some((4_999, 4_999)),
454 calculate_withdraw_amounts(10_000, 10_000, 100_000, 50_000)
455 );
456
457 assert_eq!(
458 Some((4_999_999_999, 4_999_999_999)),
459 calculate_withdraw_amounts(10_000_000_000, 10_000_000_000, 100_000_000_000, 50_000_000_000)
460 );
461
462 assert_eq!(
463 Some((5_000, 5_000)),
464 calculate_withdraw_amounts(10_000, 10_000, 100_000, 50_001)
465 );
466
467 assert_eq!(
468 Some((5_000, 2_500)),
469 calculate_withdraw_amounts(10_000, 5_000, 100_000, 50_001)
470 );
471
472 assert_eq!(
473 Some((2_500, 5_000)),
474 calculate_withdraw_amounts(5_000, 10_000, 100_000, 50_001)
475 );
476
477 assert_eq!(
478 Some((0, 0)),
479 calculate_withdraw_amounts(5_000, 10_000, 100_000, 1)
480 );
481
482 assert_eq!(
483 Some((49, 29)),
484 calculate_withdraw_amounts(5_000_000, 3_000_000, 100_000, 1)
485 );
486
487 assert_eq!(
488 Some((0, 0)),
489 calculate_withdraw_amounts(10, 10, 100, 9)
490 );
491
492 }
495}