1use ethers::types::{I256, U256};
2use eyre::{eyre, Result};
3use fixedpointmath::{fixed, FixedPoint};
4
5use crate::calculate_effective_share_reserves;
6
7pub trait YieldSpace {
8 fn ze(&self) -> Result<FixedPoint<U256>> {
10 calculate_effective_share_reserves(self.z(), self.zeta())
11 }
12
13 fn z(&self) -> FixedPoint<U256>;
15
16 fn zeta(&self) -> I256;
18
19 fn y(&self) -> FixedPoint<U256>;
21
22 fn c(&self) -> FixedPoint<U256>;
24
25 fn mu(&self) -> FixedPoint<U256>;
27
28 fn t(&self) -> FixedPoint<U256>;
30
31 fn calculate_spot_price(&self) -> Result<FixedPoint<U256>> {
33 if self.y() <= fixed!(0) {
34 return Err(eyre!("expected y={} > 0", self.y()));
35 }
36 ((self.mu() * self.ze()?) / self.y()).pow(self.t())
37 }
38
39 fn calculate_bonds_out_given_shares_in_down(
43 &self,
44 dz: FixedPoint<U256>,
45 ) -> Result<FixedPoint<U256>> {
46 let k = self.k_up()?;
50
51 let mut ze = (self.mu() * (self.ze()? + dz)).pow(fixed!(1e18) - self.t())?;
55 ze = self.c().mul_div_down(ze, self.mu());
57
58 if k < ze {
62 return Err(eyre!("expected k={} >= ze={}", k, ze));
63 }
64 let mut y = k - ze;
65 if y >= fixed!(1e18) {
66 y = y.pow(fixed!(1e18).div_up(fixed!(1e18) - self.t()))?;
68 } else {
69 y = y.pow(fixed!(1e18) / (fixed!(1e18) - self.t()))?;
71 }
72
73 if self.y() < y {
75 return Err(eyre!("expected y={} >= delta_y={}", self.y(), y));
76 }
77 Ok(self.y() - y)
78 }
79
80 fn calculate_shares_in_given_bonds_out_up(
83 &self,
84 dy: FixedPoint<U256>,
85 ) -> Result<FixedPoint<U256>> {
86 let k = self.k_up()?;
90
91 if self.y() < dy {
93 return Err(eyre!(
94 "calculate_shares_in_given_bonds_out_up: y = {} < {} = dy",
95 self.y(),
96 dy,
97 ));
98 }
99 let y = (self.y() - dy).pow(fixed!(1e18) - self.t())?;
100
101 if k < y {
105 return Err(eyre!(
106 "calculate_shares_in_given_bonds_out_up: k = {} < {} = y",
107 k,
108 y,
109 ));
110 }
111 let mut _z = (k - y).mul_div_up(self.mu(), self.c());
112 if _z >= fixed!(1e18) {
113 _z = _z.pow(fixed!(1e18).div_up(fixed!(1e18) - self.t()))?;
115 } else {
116 _z = _z.pow(fixed!(1e18) / (fixed!(1e18) - self.t()))?;
118 }
119 _z = _z.div_up(self.mu());
121
122 if _z < self.ze()? {
124 return Err(eyre!(
125 "calculate_shares_in_given_bonds_out_up: _z = {} < {} = ze",
126 _z,
127 self.ze()?,
128 ));
129 }
130 Ok(_z - self.ze()?)
131 }
132
133 fn calculate_shares_in_given_bonds_out_down(
136 &self,
137 dy: FixedPoint<U256>,
138 ) -> Result<FixedPoint<U256>> {
139 let k = self.k_down()?;
143
144 let y = (self.y() - dy).pow(fixed!(1e18) - self.t())?;
146
147 let mut ze = (k - y).mul_div_down(self.mu(), self.c());
151 if ze >= fixed!(1e18) {
152 ze = ze.pow(fixed!(1e18) / (fixed!(1e18) - self.t()))?;
154 } else {
155 ze = ze.pow(fixed!(1e18).div_up(fixed!(1e18) - self.t()))?;
157 }
158 ze /= self.mu();
160
161 Ok(ze - self.ze()?)
163 }
164
165 fn calculate_shares_out_given_bonds_in_down(
170 &self,
171 dy: FixedPoint<U256>,
172 ) -> Result<FixedPoint<U256>> {
173 let k = self.k_up()?;
177
178 let y = (self.y() + dy).pow(fixed!(1e18) - self.t())?;
180
181 if k < y {
183 return Err(eyre!(
184 "calculate_shares_out_given_bonds_in_down: k = {} < {} = y",
185 k,
186 y
187 ));
188 }
189
190 let mut ze = (k - y).mul_div_up(self.mu(), self.c());
194 if ze >= fixed!(1e18) {
195 ze = ze.pow(fixed!(1e18).div_up(fixed!(1e18) - self.t()))?;
197 } else {
198 ze = ze.pow(fixed!(1e18) / (fixed!(1e18) - self.t()))?;
200 }
201 ze = ze.div_up(self.mu());
203
204 if self.ze()? > ze {
206 Ok(self.ze()? - ze)
207 } else {
208 Ok(fixed!(0))
209 }
210 }
211
212 fn calculate_max_buy_shares_in(&self) -> Result<FixedPoint<U256>> {
215 let k = self.k_down()?;
226 let mut optimal_ze = k.div_down(self.c().div_up(self.mu()) + fixed!(1e18));
227 if optimal_ze >= fixed!(1e18) {
228 optimal_ze = optimal_ze.pow(fixed!(1e18).div_down(fixed!(1e18) - self.t()))?;
230 } else {
231 optimal_ze = optimal_ze.pow(fixed!(1e18) / (fixed!(1e18) - self.t()))?;
233 }
234 optimal_ze = optimal_ze.div_down(self.mu());
235
236 if optimal_ze >= self.ze()? {
239 Ok(optimal_ze - self.ze()?)
240 } else {
241 Err(eyre!(
242 "calculate_max_buy_shares_in: optimal_ze = {} < {} = ze",
243 optimal_ze,
244 self.ze()?,
245 ))
246 }
247 }
248
249 fn calculate_max_buy_bonds_out(&self) -> Result<FixedPoint<U256>> {
253 let k = self.k_up()?;
261 let mut optimal_y = k.div_up(self.c() / self.mu() + fixed!(1e18));
262 if optimal_y >= fixed!(1e18) {
263 optimal_y = optimal_y.pow(fixed!(1e18).div_up(fixed!(1e18) - self.t()))?;
265 } else {
266 optimal_y = optimal_y.pow(fixed!(1e18) / (fixed!(1e18) - self.t()))?;
268 }
269
270 if self.y() >= optimal_y {
273 Ok(self.y() - optimal_y)
274 } else {
275 Err(eyre!(
276 "calculate_max_buy_bonds_out: y = {} < {} = optimal_y",
277 self.y(),
278 optimal_y,
279 ))
280 }
281 }
282
283 fn calculate_max_sell_bonds_in(&self, mut z_min: FixedPoint<U256>) -> Result<FixedPoint<U256>> {
287 if self.zeta() < I256::zero() {
292 z_min += FixedPoint::try_from(-self.zeta())?;
293 }
294
295 let k = self.k_down()?;
302 let mut optimal_y = k - self.c().mul_div_up(
303 self.mu().mul_up(z_min).pow(fixed!(1e18) - self.t())?,
304 self.mu(),
305 );
306 if optimal_y >= fixed!(1e18) {
307 optimal_y = optimal_y.pow(fixed!(1e18) / (fixed!(1e18) - self.t()))?;
309 } else {
310 optimal_y = optimal_y.pow(fixed!(1e18).div_up(fixed!(1e18) - self.t()))?;
312 }
313
314 if optimal_y >= self.y() {
317 Ok(optimal_y - self.y())
318 } else {
319 Err(eyre!(
320 "calculate_max_sell_bonds_in: optimal_y = {} < {} = y",
321 optimal_y,
322 self.y(),
323 ))
324 }
325 }
326
327 fn k_up(&self) -> Result<FixedPoint<U256>> {
333 Ok(self.c().mul_div_up(
334 (self.mu().mul_up(self.ze()?)).pow(fixed!(1e18) - self.t())?,
335 self.mu(),
336 ) + self.y().pow(fixed!(1e18) - self.t())?)
337 }
338
339 fn k_down(&self) -> Result<FixedPoint<U256>> {
345 Ok(self.c().mul_div_down(
346 (self.mu() * self.ze()?).pow(fixed!(1e18) - self.t())?,
347 self.mu(),
348 ) + self.y().pow(fixed!(1e18) - self.t())?)
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use std::panic;
355
356 use hyperdrive_test_utils::{chain::TestChain, constants::FAST_FUZZ_RUNS};
357 use rand::{thread_rng, Rng};
358
359 use super::*;
360 use crate::State;
361
362 #[tokio::test]
363 async fn fuzz_calculate_bonds_out_given_shares_in() -> Result<()> {
364 let chain = TestChain::new().await?;
365
366 let mut rng = thread_rng();
368 for _ in 0..*FAST_FUZZ_RUNS {
369 let state = rng.gen::<State>();
370 let in_ = rng.gen::<FixedPoint<U256>>();
371 let actual =
373 panic::catch_unwind(|| state.calculate_bonds_out_given_shares_in_down(in_));
374 match chain
375 .mock_yield_space_math()
376 .calculate_bonds_out_given_shares_in_down(
377 state.ze()?.into(),
378 state.y().into(),
379 in_.into(),
380 (fixed!(1e18) - state.t()).into(),
381 state.c().into(),
382 state.mu().into(),
383 )
384 .call()
385 .await
386 {
387 Ok(expected) => assert_eq!(actual.unwrap().unwrap(), FixedPoint::from(expected)),
388 Err(_) => assert!(actual.is_err() || actual.unwrap().is_err()),
389 }
390 }
391
392 Ok(())
393 }
394
395 #[tokio::test]
396 async fn fuzz_calculate_shares_in_given_bonds_out_up() -> Result<()> {
397 let chain = TestChain::new().await?;
398
399 let mut rng = thread_rng();
401 for _ in 0..*FAST_FUZZ_RUNS {
402 let state = rng.gen::<State>();
403 let in_ = rng.gen::<FixedPoint<U256>>();
404 let actual = state.calculate_shares_in_given_bonds_out_up(in_);
405 match chain
406 .mock_yield_space_math()
407 .calculate_shares_in_given_bonds_out_up(
408 state.ze()?.into(),
409 state.y().into(),
410 in_.into(),
411 (fixed!(1e18) - state.t()).into(),
412 state.c().into(),
413 state.mu().into(),
414 )
415 .call()
416 .await
417 {
418 Ok(expected) => {
419 assert_eq!(actual.unwrap(), FixedPoint::from(expected));
420 }
421 Err(_) => assert!(actual.is_err()),
422 }
423 }
424
425 Ok(())
426 }
427
428 #[tokio::test]
429 async fn fuzz_calculate_shares_in_given_bonds_out_down() -> Result<()> {
430 let chain = TestChain::new().await?;
431
432 let mut rng = thread_rng();
434 for _ in 0..*FAST_FUZZ_RUNS {
435 let state = rng.gen::<State>();
436 let out = rng.gen::<FixedPoint<U256>>();
437 let actual =
438 panic::catch_unwind(|| state.calculate_shares_in_given_bonds_out_down(out));
439 match chain
440 .mock_yield_space_math()
441 .calculate_shares_in_given_bonds_out_down(
442 state.ze()?.into(),
443 state.y().into(),
444 out.into(),
445 (fixed!(1e18) - state.t()).into(),
446 state.c().into(),
447 state.mu().into(),
448 )
449 .call()
450 .await
451 {
452 Ok(expected) => {
453 assert_eq!(actual.unwrap().unwrap(), FixedPoint::from(expected));
454 }
455 Err(_) => assert!(actual.is_err() || actual.unwrap().is_err()),
456 }
457 }
458
459 Ok(())
460 }
461
462 #[tokio::test]
463 async fn fuzz_calculate_shares_out_given_bonds_in_down() -> Result<()> {
464 let chain = TestChain::new().await?;
465
466 let mut rng = thread_rng();
468 for _ in 0..*FAST_FUZZ_RUNS {
469 let state = rng.gen::<State>();
470 let in_ = rng.gen::<FixedPoint<U256>>();
471 let actual = state.calculate_shares_out_given_bonds_in_down(in_);
472 match chain
473 .mock_yield_space_math()
474 .calculate_shares_out_given_bonds_in_down_safe(
475 state.ze()?.into(),
476 state.y().into(),
477 in_.into(),
478 (fixed!(1e18) - state.t()).into(),
479 state.c().into(),
480 state.mu().into(),
481 )
482 .call()
483 .await
484 {
485 Ok((expected_out, expected_status)) => {
486 assert_eq!(actual.is_ok(), expected_status);
487 assert_eq!(actual.unwrap_or(fixed!(0)), FixedPoint::from(expected_out));
488 }
489 Err(_) => assert!(actual.is_err()),
490 }
491 }
492
493 Ok(())
494 }
495
496 #[tokio::test]
497 async fn fuzz_calculate_max_buy_shares_in() -> Result<()> {
498 let chain = TestChain::new().await?;
499
500 let mut rng = thread_rng();
502 for _ in 0..*FAST_FUZZ_RUNS {
503 let state = rng.gen::<State>();
504 let actual = state.calculate_max_buy_shares_in();
505 match chain
506 .mock_yield_space_math()
507 .calculate_max_buy_shares_in_safe(
508 state.ze()?.into(),
509 state.y().into(),
510 (fixed!(1e18) - state.t()).into(),
511 state.c().into(),
512 state.mu().into(),
513 )
514 .call()
515 .await
516 {
517 Ok((expected_out, expected_status)) => {
518 assert_eq!(actual.is_ok(), expected_status);
519 assert_eq!(actual.unwrap_or(fixed!(0)), FixedPoint::from(expected_out));
520 }
521 Err(_) => assert!(actual.is_err()),
522 }
523 }
524
525 Ok(())
526 }
527
528 #[tokio::test]
529 async fn fuzz_calculate_max_buy_bounds_out() -> Result<()> {
530 let chain = TestChain::new().await?;
531
532 let mut rng = thread_rng();
534 for _ in 0..*FAST_FUZZ_RUNS {
535 let state = rng.gen::<State>();
536 let actual = state.calculate_max_buy_bonds_out();
537 match chain
538 .mock_yield_space_math()
539 .calculate_max_buy_bonds_out_safe(
540 state.ze()?.into(),
541 state.y().into(),
542 (fixed!(1e18) - state.t()).into(),
543 state.c().into(),
544 state.mu().into(),
545 )
546 .call()
547 .await
548 {
549 Ok((expected_out, expected_status)) => {
550 assert_eq!(actual.is_ok(), expected_status);
551 assert_eq!(actual.unwrap_or(fixed!(0)), FixedPoint::from(expected_out));
552 }
553 Err(_) => assert!(actual.is_err()),
554 }
555 }
556
557 Ok(())
558 }
559
560 #[tokio::test]
561 async fn fuzz_calculate_max_sell_bonds_in() -> Result<()> {
562 let chain = TestChain::new().await?;
563
564 let mut rng = thread_rng();
566 for _ in 0..*FAST_FUZZ_RUNS {
567 let state = rng.gen::<State>();
568 let z_min = rng.gen::<FixedPoint<U256>>();
569 let actual = panic::catch_unwind(|| state.calculate_max_sell_bonds_in(z_min));
570 match chain
571 .mock_yield_space_math()
572 .calculate_max_sell_bonds_in_safe(
573 state.z().into(),
574 state.zeta(),
575 state.y().into(),
576 z_min.into(),
577 (fixed!(1e18) - state.t()).into(),
578 state.c().into(),
579 state.mu().into(),
580 )
581 .call()
582 .await
583 {
584 Ok((expected_out, expected_status)) => {
585 let actual = actual.unwrap();
586 assert_eq!(actual.is_ok(), expected_status);
587 assert_eq!(actual.unwrap_or(fixed!(0)), FixedPoint::from(expected_out));
588 }
589 Err(e) => assert!(actual.is_err() || actual.unwrap().is_err()),
590 }
591 }
592
593 Ok(())
594 }
595
596 #[tokio::test]
597 async fn fuzz_k_down() -> Result<()> {
598 let chain = TestChain::new().await?;
599
600 let mut rng = thread_rng();
602 for _ in 0..*FAST_FUZZ_RUNS {
603 let state = rng.gen::<State>();
604 let actual = state.k_down();
605 match chain
606 .mock_yield_space_math()
607 .k_down(
608 state.ze()?.into(),
609 state.y().into(),
610 (fixed!(1e18) - state.t()).into(),
611 state.c().into(),
612 state.mu().into(),
613 )
614 .call()
615 .await
616 {
617 Ok(expected) => assert_eq!(actual.unwrap(), FixedPoint::from(expected)),
618 Err(_) => assert!(actual.is_err()),
619 }
620 }
621
622 Ok(())
623 }
624
625 #[tokio::test]
626 async fn fuzz_k_up() -> Result<()> {
627 let chain = TestChain::new().await?;
628
629 let mut rng = thread_rng();
631 for _ in 0..*FAST_FUZZ_RUNS {
632 let state = rng.gen::<State>();
633 let actual = state.k_up();
634 match chain
635 .mock_yield_space_math()
636 .k_up(
637 state.ze()?.into(),
638 state.y().into(),
639 (fixed!(1e18) - state.t()).into(),
640 state.c().into(),
641 state.mu().into(),
642 )
643 .call()
644 .await
645 {
646 Ok(expected) => assert_eq!(actual.unwrap(), FixedPoint::from(expected)),
647 Err(_) => assert!(actual.is_err()),
648 }
649 }
650
651 Ok(())
652 }
653}