1#![no_std]
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum CurveError {
6 Overflow,
8 ZeroInput,
10 EmptyPool,
12 InvalidFee,
14 InsufficientLpSupply,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct DepositResult {
22 pub amount_x_used: u64,
23 pub amount_y_used: u64,
24 pub lp_minted: u64,
25}
26
27pub fn swap_output(
33 reserve_in: u64,
34 reserve_out: u64,
35 amount_in: u64,
36) -> Result<u64, CurveError> {
37 if amount_in == 0 {
38 return Err(CurveError::ZeroInput);
39 }
40
41 if reserve_in == 0 || reserve_out == 0 {
42 return Err(CurveError::EmptyPool);
43 }
44
45 let reserve_in = reserve_in as u128;
46 let reserve_out = reserve_out as u128;
47 let amount_in = amount_in as u128;
48
49 let numerator = amount_in
50 .checked_mul(reserve_out)
51 .ok_or(CurveError::Overflow)?;
52
53 let denominator = reserve_in
54 .checked_add(amount_in)
55 .ok_or(CurveError::Overflow)?;
56
57 let amount_out = numerator / denominator;
58
59 u64::try_from(amount_out).map_err(|_| CurveError::Overflow)
60
61}
62
63pub fn swap_output_with_fee(
66 reserve_in: u64,
67 reserve_out: u64,
68 amount_in: u64,
69 fee_bps: u16,
70) -> Result<u64, CurveError> {
71 if amount_in == 0 {
72 return Err(CurveError::ZeroInput);
73 }
74
75 if reserve_in == 0 || reserve_out == 0 {
76 return Err(CurveError::EmptyPool);
77 }
78
79 if fee_bps >= 10_000 {
80 return Err(CurveError::InvalidFee);
81 }
82
83 let reserve_in = reserve_in as u128;
84 let reserve_out = reserve_out as u128;
85 let amount_in = amount_in as u128;
86 let fee_bps = fee_bps as u128;
87
88 let amount_in_with_fee = amount_in
89 .checked_mul(10_000 - fee_bps)
90 .ok_or(CurveError::Overflow)?;
91
92 let numerator = amount_in_with_fee
93 .checked_mul(reserve_out)
94 .ok_or(CurveError::Overflow)?;
95
96 let reserve_in_scaled = reserve_in
97 .checked_mul(10_000)
98 .ok_or(CurveError::Overflow)?;
99
100 let denominator = reserve_in_scaled
101 .checked_add(amount_in_with_fee)
102 .ok_or(CurveError::Overflow)?;
103
104 let amount_out = numerator / denominator;
105
106 u64::try_from(amount_out).map_err(|_| CurveError::Overflow)
107
108}
109
110pub fn deposit_amounts(
115 reserve_x: u64,
116 reserve_y: u64,
117 total_lp: u64,
118 amount_x_in: u64,
119 amount_y_in: u64,
120) -> Result<DepositResult, CurveError> {
121 if amount_x_in == 0 || amount_y_in == 0 {
122 return Err(CurveError::ZeroInput);
123 }
124
125 if total_lp == 0 {
126 let product = (amount_x_in as u128) * (amount_y_in as u128);
127 let lp = integer_sqrt(product);
128 let lp_minted = u64::try_from(lp).map_err(|_| CurveError::Overflow)?;
129 return Ok(DepositResult { amount_x_used: amount_x_in, amount_y_used: amount_y_in, lp_minted });
130 }
131
132 if reserve_x == 0 || reserve_y == 0 {
133 return Err(CurveError::EmptyPool);
134 }
135
136 let reserve_x = reserve_x as u128;
137 let reserve_y = reserve_y as u128;
138 let total_lp = total_lp as u128;
139 let amount_x_in = amount_x_in as u128;
140 let amount_y_in = amount_y_in as u128;
141
142 let amount_y_optimal = amount_x_in
143 .checked_mul(reserve_y)
144 .ok_or(CurveError::Overflow)?
145 / reserve_x;
146
147 let (amount_x_used, amount_y_used) = if amount_y_optimal <= amount_y_in {
148 (amount_x_in, amount_y_optimal)
149 } else {
150 let amount_x_optimal = amount_y_in
151 .checked_mul(reserve_x)
152 .ok_or(CurveError::Overflow)? / reserve_y;
153 (amount_x_optimal, amount_y_in)
154 };
155
156 let lp_from_x = amount_x_used
157 .checked_mul(total_lp)
158 .ok_or(CurveError::Overflow)? / reserve_x;
159
160 let lp_from_y = amount_y_used
161 .checked_mul(total_lp)
162 .ok_or(CurveError::Overflow)? / reserve_y;
163
164 let lp_minted = lp_from_x.min(lp_from_y);
165
166 Ok(DepositResult {
167 amount_x_used: u64::try_from(amount_x_used).map_err(|_| CurveError::Overflow)?,
168 amount_y_used: u64::try_from(amount_y_used).map_err(|_| CurveError::Overflow)?,
169 lp_minted: u64::try_from(lp_minted).map_err(|_| CurveError::Overflow)?
170 })
171}
172
173pub fn withdraw_amounts(
176 reserve_x: u64,
177 reserve_y: u64,
178 total_lp: u64,
179 lp_burn: u64,
180) -> Result<(u64, u64), CurveError> {
181 if lp_burn == 0 {
182 return Err(CurveError::ZeroInput);
183 }
184
185 if total_lp == 0 || lp_burn > total_lp {
186 return Err(CurveError::InsufficientLpSupply);
187 }
188
189 let reserve_x = reserve_x as u128;
190 let reserve_y = reserve_y as u128;
191 let total_lp = total_lp as u128;
192 let lp_burn = lp_burn as u128;
193
194 let x_out = lp_burn
195 .checked_mul(reserve_x)
196 .ok_or(CurveError::Overflow)?
197 / total_lp;
198
199 let y_out = lp_burn
200 .checked_mul(reserve_y)
201 .ok_or(CurveError::Overflow)?
202 / total_lp;
203
204 let x_out = u64::try_from(x_out).map_err(|_| CurveError::Overflow)?;
205 let y_out = u64::try_from(y_out).map_err(|_| CurveError::Overflow)?;
206
207 Ok((x_out, y_out))
208}
209
210pub fn integer_sqrt(value: u128) -> u128 {
214 if value == 0 {
215 return 0;
216 }
217
218 if value < 4 {
219 return 1;
220 }
221
222 let mut z = value;
223 let mut x = value / 2 + 1;
224
225 while x < z {
226 z = x;
227 x = (value / x + x) / 2;
228 }
229 z
230}
231
232
233#[cfg(test)]
234mod tests {
235 use core::u128;
236
237 use super::*;
238
239 #[test]
240 fn lifecycle_deposit_withdraw_no_swap_round_trips_exact() {
241 let dep = deposit_amounts(0, 0, 0, 1000, 1000).unwrap();
242 assert_eq!(
243 dep,
244 DepositResult { amount_x_used: 1000, amount_y_used: 1000, lp_minted: 1000 },
245 );
246
247 let (x_out, y_out) =
248 withdraw_amounts(dep.amount_x_used, dep.amount_y_used, dep.lp_minted, dep.lp_minted)
249 .unwrap();
250 assert_eq!((x_out, y_out), (1000, 1000));
251 }
252
253 #[test]
254 fn lifecycle_two_lps_proportional_withdrawal() {
255 let alice = deposit_amounts(0, 0, 0, 1000, 1000).unwrap();
256 let (mut rx, mut ry, mut tlp) =
257 (alice.amount_x_used, alice.amount_y_used, alice.lp_minted);
258
259 let bob = deposit_amounts(rx, ry, tlp, 500, 500).unwrap();
260 rx += bob.amount_x_used;
261 ry += bob.amount_y_used;
262 tlp += bob.lp_minted;
263 assert_eq!((rx, ry, tlp), (1500, 1500, 1500));
264
265 let (ax, ay) = withdraw_amounts(rx, ry, tlp, alice.lp_minted).unwrap();
266 assert_eq!((ax, ay), (1000, 1000));
267 rx -= ax;
268 ry -= ay;
269 tlp -= alice.lp_minted;
270
271 let (bx, by) = withdraw_amounts(rx, ry, tlp, bob.lp_minted).unwrap();
272 assert_eq!((bx, by), (500, 500));
273 }
274
275 #[test]
276 fn lifecycle_swap_grows_lp_value() {
277 let dep = deposit_amounts(0, 0, 0, 1000, 1000).unwrap();
278 let (mut rx, mut ry) = (dep.amount_x_used, dep.amount_y_used);
279 let tlp = dep.lp_minted;
280
281 let amount_out = swap_output_with_fee(rx, ry, 100, 30).unwrap();
282 rx += 100;
283 ry -= amount_out;
284
285 let (x_out, y_out) = withdraw_amounts(rx, ry, tlp, tlp).unwrap();
286
287 let value_out = (x_out as u128) * (y_out as u128);
288 let value_in = 1000u128 * 1000u128;
289 assert!(value_out > value_in);
290 }
291
292 #[test]
293 fn lifecycle_late_lp_does_not_steal_earlier_fees() {
294 let alice = deposit_amounts(0, 0, 0, 1000, 1000).unwrap();
295 let (mut rx, mut ry, mut tlp) =
296 (alice.amount_x_used, alice.amount_y_used, alice.lp_minted);
297
298 let bob_out = swap_output_with_fee(rx, ry, 100, 30).unwrap();
299 rx += 100;
300 ry -= bob_out;
301
302 let charlie = deposit_amounts(rx, ry, tlp, 500, 500).unwrap();
303 let charlie_in_x = charlie.amount_x_used;
304 let charlie_in_y = charlie.amount_y_used;
305 rx += charlie_in_x;
306 ry += charlie_in_y;
307 tlp += charlie.lp_minted;
308
309 let (ax, ay) = withdraw_amounts(rx, ry, tlp, alice.lp_minted).unwrap();
310 assert!((ax as u128) * (ay as u128) > 1_000_000);
311 rx -= ax;
312 ry -= ay;
313 tlp -= alice.lp_minted;
314
315 let (cx, cy) = withdraw_amounts(rx, ry, tlp, charlie.lp_minted).unwrap();
316 assert!(cx <= charlie_in_x);
317 assert!(charlie_in_x - cx <= 2);
318 assert!(cy <= charlie_in_y);
319 assert!(charlie_in_y - cy <= 2);
320 }
321
322 #[test]
323 fn lifecycle_extreme_inputs_dont_panic() {
324 let big: u64 = 10_u64.pow(15);
325
326 let dep = deposit_amounts(0, 0, 0, big, big).unwrap();
327 let (mut rx, mut ry, mut tlp) = (dep.amount_x_used, dep.amount_y_used, dep.lp_minted);
328
329 let out = swap_output_with_fee(rx, ry, big / 10, 30).unwrap();
330 rx += big / 10;
331 ry -= out;
332
333 let dep2 = deposit_amounts(rx, ry, tlp, big / 2, big / 2).unwrap();
334 rx += dep2.amount_x_used;
335 ry += dep2.amount_y_used;
336 tlp += dep2.lp_minted;
337
338 let (x, y) = withdraw_amounts(rx, ry, tlp, tlp / 2).unwrap();
339 assert!(x > 0);
340 assert!(y > 0);
341 }
342
343 #[test]
344 fn swap_basic_no_fee() {
345 assert_eq!(swap_output(100, 100, 50), Ok(33));
346 }
347
348 #[test]
349 fn swap_zero_input_error() {
350 assert_eq!(swap_output(100, 100, 0), Err(CurveError::ZeroInput));
351 }
352
353 #[test]
354 fn swap_empty_pool_errors() {
355 assert_eq!(swap_output(0, 100, 50), Err(CurveError::EmptyPool));
356 assert_eq!(swap_output(100, 0, 50), Err(CurveError::EmptyPool));
357 }
358
359 #[test]
360 fn swap_big_numbers_need_u128() {
361 assert_eq!(swap_output(10_000_000_000, 10_000_000_000, 10_000_000_000), Ok(5_000_000_000));
362 }
363
364 #[test]
365 fn swap_at_u64_max_does_not_panic() {
366 let result = swap_output(u64::MAX, u64::MAX, u64::MAX);
367 assert!(result.is_ok());
368 assert!(result.unwrap() < u64::MAX);
369 }
370
371 #[test]
372 fn swap_preserves_invariant() {
373 let reserve_x: u64 = 1000;
374 let reserve_y: u64 = 1000;
375 let amount_in: u64 = 250;
376
377 let amount_out = swap_output(reserve_x, reserve_y, amount_in).unwrap();
378
379 let new_x = reserve_x + amount_in;
380 let new_y = reserve_y - amount_out;
381
382 let old_k = (reserve_x as u128) * (reserve_y as u128);
383 let new_k = (new_x as u128) * (new_y as u128);
384
385 assert!(new_k >= old_k);
386 }
387
388 #[test]
389 fn swap_tiny_input() {
390 assert_eq!(swap_output(1_000_000, 1_000_000, 1), Ok(0));
391 }
392
393 #[test]
394 fn swap_drains_almost_all() {
395 let amount_out = swap_output(1000, 1000, 1_000_000).unwrap();
396 assert_eq!(amount_out, 999);
397 assert!(amount_out < 1000);
398 }
399
400 #[test]
401 fn swap_with_fee_matches_no_fee_when_zero() {
402 let no_fee = swap_output(1000, 1000, 250).unwrap();
403 let zero_fee = swap_output_with_fee(1000, 1000, 250, 0).unwrap();
404
405 assert_eq!(no_fee, zero_fee);
406 }
407
408 #[test]
409 fn swap_with_fee_30_bps_basic() {
410 assert_eq!(swap_output_with_fee(1000, 1000, 100, 30), Ok(90));
411 }
412
413 #[test]
414 fn swap_with_fee_invalid_fee_errors() {
415 assert_eq!(swap_output_with_fee(1000, 1000, 100, 10_000), Err(CurveError::InvalidFee));
416 assert_eq!(swap_output_with_fee(1000, 1000, 100, 10_001), Err(CurveError::InvalidFee));
417 }
418
419 #[test]
420 fn swap_with_fee_preserves_invariant() {
421 let reserve_x: u64 = 1_000_000;
422 let reserve_y: u64 = 1_000_000;
423 let amount_in: u64 = 250_000;
424 let fee_bps: u16 = 30;
425
426 let amount_out = swap_output_with_fee(reserve_x, reserve_y, amount_in, fee_bps).unwrap();
427
428 let new_x = reserve_x + amount_in;
429 let new_y = reserve_y - amount_out;
430
431 let old_k = (reserve_x as u128) * (reserve_y as u128);
432 let new_k = (new_x as u128) * (new_y as u128);
433
434 assert!(new_k > old_k);
435 }
436
437 #[test]
438 fn sqrt_zero() {
439 assert_eq!(integer_sqrt(0), 0);
440 }
441
442 #[test]
443 fn sqrt_small() {
444 assert_eq!(integer_sqrt(1), 1);
445 assert_eq!(integer_sqrt(2), 1);
446 assert_eq!(integer_sqrt(3), 1);
447 assert_eq!(integer_sqrt(4), 2);
448 }
449
450 #[test]
451 fn sqrt_perfect_sqaures() {
452 assert_eq!(integer_sqrt(9), 3);
453 assert_eq!(integer_sqrt(16), 4);
454 assert_eq!(integer_sqrt(100), 10);
455 assert_eq!(integer_sqrt(1_000_000), 1000);
456 }
457
458 #[test]
459 fn sqrt_non_perfect_floors() {
460 assert_eq!(integer_sqrt(15), 3);
461 assert_eq!(integer_sqrt(99), 9);
462 assert_eq!(integer_sqrt(1_000_001), 1000);
463 }
464
465 #[test]
466 fn sqrt_big() {
467 assert_eq!(integer_sqrt(1_000_000_000_000_000_000), 1_000_000_000);
468 }
469
470 #[test]
471 fn sqrt_property() {
472 for value in [50u128, 99, 1000, 1_000_001, 12_345_678] {
473 let r = integer_sqrt(value);
474 assert!(r * r <= value);
475 assert!((r + 1) * (r + 1) > value);
476 }
477 }
478
479 #[test]
480 fn sqrt_u128_max_does_not_panic() {
481 let r = integer_sqrt(u128::MAX);
482 assert_eq!(r, u64::MAX as u128);
483 }
484
485 #[test]
486 fn deposit_first_simple() {
487 let r = deposit_amounts(0, 0, 0, 100, 100).unwrap();
488 assert_eq!(r, DepositResult {amount_x_used: 100, amount_y_used: 100, lp_minted: 100});
489 }
490
491 #[test]
492 fn deposit_first_asymmetric() {
493 let r = deposit_amounts(0, 0, 0, 200, 50).unwrap();
494 assert_eq!(r, DepositResult {amount_x_used: 200, amount_y_used: 50, lp_minted: 100});
495 }
496
497 #[test]
498 fn deposit_proportional() {
499 let r = deposit_amounts(1000, 1000, 1000, 100, 100).unwrap();
500 assert_eq!(r, DepositResult {amount_x_used: 100, amount_y_used: 100, lp_minted: 100});
501 }
502
503 #[test]
504 fn deposit_x_constrained() {
505 let r = deposit_amounts(1000, 2000, 1000, 100, 100).unwrap();
506 assert_eq!(r, DepositResult {amount_x_used: 50, amount_y_used: 100, lp_minted: 50});
507 }
508
509 #[test]
510 fn deposit_y_constrained() {
511 let r = deposit_amounts(1000, 2000, 1000, 100, 500).unwrap();
512 assert_eq!(r, DepositResult { amount_x_used: 100, amount_y_used: 200, lp_minted: 100 });
513 }
514
515 #[test]
516 fn deposit_zero_input_errors() {
517 assert_eq!(deposit_amounts(1000, 1000, 1000, 0, 100), Err(CurveError::ZeroInput));
518 assert_eq!(deposit_amounts(1000, 1000, 1000, 100, 0), Err(CurveError::ZeroInput));
519 }
520
521 #[test]
522 fn deposit_inconsistent_pool_errors() {
523 assert_eq!(deposit_amounts(0, 100, 1000, 50, 50), Err(CurveError::EmptyPool));
524 assert_eq!(deposit_amounts(100, 0, 1000, 50, 50), Err(CurveError::EmptyPool));
525 }
526
527 #[test]
528 fn withdraw_proportional() {
529 assert_eq!(withdraw_amounts(1000, 1000, 1000, 500), Ok((500, 500)));
530 }
531
532 #[test]
533 fn withdraw_zero_lp_errors() {
534 assert_eq!(withdraw_amounts(1000, 1000, 1000, 0), Err(CurveError::ZeroInput));
535 }
536
537 #[test]
538 fn withdraw_full() {
539 assert_eq!(withdraw_amounts(1000, 1000, 1000, 1000), Ok((1000, 1000)));
540 }
541
542 #[test]
543 fn withdraw_asymmetric_reserves() {
544 assert_eq!(withdraw_amounts(1000, 2000, 1000, 250), Ok((250, 500)));
545 }
546
547 #[test]
548 fn withdraw_to_much_errors() {
549 assert_eq!(withdraw_amounts(1000, 1000, 1000, 1001), Err(CurveError::InsufficientLpSupply));
550 }
551
552 #[test]
553 fn withdraw_empty_supply() {
554 assert_eq!(withdraw_amounts(0, 0, 0, 50), Err(CurveError::InsufficientLpSupply));
555 }
556
557 #[test]
558 fn withdraw_rounds_down() {
559 assert_eq!(withdraw_amounts(100, 100, 3, 1), Ok((33, 33)));
560 }
561}