hyperdrive_math/lp/
math.rs

1use std::cmp::{max, min, Ordering};
2
3use ethers::types::{I256, U256};
4use eyre::{eyre, Result};
5use fixedpointmath::{fixed, int256, FixedPoint};
6
7use crate::{calculate_effective_share_reserves, State, YieldSpace};
8
9pub static SHARE_PROCEEDS_MAX_ITERATIONS: u64 = 4;
10pub static SHARE_PROCEEDS_SHORT_CIRCUIT_TOLERANCE: u128 = 1_000_000_000; // 1e9
11pub static SHARE_PROCEEDS_TOLERANCE: u128 = 100_000_000_000_000; // 1e14
12
13impl State {
14    /// Calculates the initial reserves. We solve for the initial reserves
15    /// by solving the following equations simultaneously:
16    ///
17    /// (1) `$c \cdot z = c \cdot z_e + p_{\text{target}} \cdot y$`
18    ///
19    /// (2) `$p_{\text{target}} = \left(\tfrac{\mu \cdot z_e}{y}\right)^{t_s}$`
20    ///
21    /// where `$p_{\text{target}}$` is the target spot price implied by the
22    /// target spot rate.
23    pub fn calculate_initial_reserves(
24        &self,
25        share_amount: FixedPoint<U256>,
26        target_apr: FixedPoint<U256>,
27    ) -> Result<(FixedPoint<U256>, I256, FixedPoint<U256>)> {
28        // NOTE: Round down to underestimate the initial bond reserves.
29        //
30        // Normalize the time to maturity to fractions of a year since the provided
31        // rate is an APR.
32        let t = self
33            .position_duration()
34            .div_down(U256::from(60 * 60 * 24 * 365).into());
35
36        // NOTE: Round up to underestimate the initial bond reserves.
37        //
38        // Calculate the target price implied by the target rate.
39        let one = fixed!(1e18);
40        let target_price = one.div_up(one + target_apr.mul_down(t));
41
42        // The share reserves is just the share amount since we are initializing
43        // the pool.
44        let share_reserves = share_amount;
45
46        // NOTE: Round down to underestimate the initial bond reserves.
47        //
48        // Calculate the initial bond reserves. This is given by:
49        //
50        // y = (mu * c * z) / (c * p_target ** (1 / t_s) + mu * p_target)
51        let bond_reserves = self.initial_vault_share_price().mul_div_down(
52            self.vault_share_price().mul_down(share_reserves),
53            self.vault_share_price()
54                .mul_down(target_price.pow(one.div_down(self.time_stretch()))?)
55                + self.initial_vault_share_price().mul_up(target_price),
56        );
57
58        // NOTE: Round down to underestimate the initial share adjustment.
59        //
60        // Calculate the initial share adjustment. This is given by:
61        //
62        // zeta = (p_target * y) / c
63        let share_adjustment =
64            I256::try_from(bond_reserves.mul_div_down(target_price, self.vault_share_price()))?;
65
66        Ok((share_reserves, share_adjustment, bond_reserves))
67    }
68
69    /// Calculates the resulting share_reserves, share_adjustment, and
70    /// bond_reserves when updating liquidity with a `share_reserves_delta`.
71    pub fn calculate_update_liquidity(
72        &self,
73        share_reserves: FixedPoint<U256>,
74        share_adjustment: I256,
75        bond_reserves: FixedPoint<U256>,
76        minimum_share_reserves: FixedPoint<U256>,
77        share_reserves_delta: I256,
78    ) -> Result<(FixedPoint<U256>, I256, FixedPoint<U256>)> {
79        // If the share reserves delta is zero, we can return early since no
80        // action is needed.
81        if share_reserves_delta == I256::zero() {
82            return Ok((share_reserves, share_adjustment, bond_reserves));
83        }
84
85        // Update the share reserves by applying the share reserves delta. We
86        // ensure that our minimum share reserves invariant is still maintained.
87        let new_share_reserves = I256::try_from(share_reserves)? + share_reserves_delta;
88        if new_share_reserves < I256::try_from(minimum_share_reserves).unwrap() {
89            return Err(eyre!(
90                "update would result in share reserves below minimum."
91            ));
92        }
93        let new_share_reserves = FixedPoint::try_from(new_share_reserves)?;
94
95        // Update the share adjustment by holding the ratio of share reserves
96        // to share adjustment proportional. In general, our pricing model cannot
97        // support negative values for the z coordinate, so this is important as
98        // it ensures that if z - zeta starts as a positive value, it ends as a
99        // positive value. With this in mind, we update the share adjustment as:
100        //
101        // zeta_old / z_old = zeta_new / z_new
102        //                  =>
103        // zeta_new = zeta_old * (z_new / z_old)
104        let new_share_adjustment = if share_adjustment >= I256::zero() {
105            let share_adjustment_fp = FixedPoint::try_from(share_adjustment)?;
106            I256::try_from(new_share_reserves.mul_div_down(share_adjustment_fp, share_reserves))?
107        } else {
108            let share_adjustment_fp = FixedPoint::try_from(-share_adjustment)?;
109            -I256::try_from(new_share_reserves.mul_div_up(share_adjustment_fp, share_reserves))?
110        };
111
112        // NOTE: Rounding down to avoid introducing dust into the computation.
113        //
114        // The liquidity update should hold the spot price invariant. The spot
115        // price of base in terms of bonds is given by:
116        //
117        // p = (mu * (z - zeta) / y) ** tau
118        //
119        // This formula implies that holding the ratio of share reserves to bond
120        // reserves constant will hold the spot price constant. This allows us
121        // to calculate the updated bond reserves as:
122        //
123        // (z_old - zeta_old) / y_old = (z_new - zeta_new) / y_new
124        //                          =>
125        // y_new = (z_new - zeta_new) * (y_old / (z_old - zeta_old))
126        let old_effective_share_reserves =
127            calculate_effective_share_reserves(self.share_reserves(), self.share_adjustment())?;
128        let new_effective_share_reserves =
129            calculate_effective_share_reserves(new_share_reserves, new_share_adjustment)?;
130        let new_bond_reserves =
131            bond_reserves.mul_div_down(new_effective_share_reserves, old_effective_share_reserves);
132
133        Ok((new_share_reserves, new_share_adjustment, new_bond_reserves))
134    }
135
136    /// Calculates the present value in shares of LP's capital in the pool.
137    pub fn calculate_present_value(
138        &self,
139        current_block_timestamp: U256,
140    ) -> Result<FixedPoint<U256>> {
141        // Calculate the average time remaining for the longs and shorts.
142
143        // To keep precision of long and short average maturity time (from contract call)
144        // we scale the block timestamp and position duration by 1e18 to calculate
145        // the normalized time remaining.
146        let long_average_time_remaining = self.calculate_scaled_normalized_time_remaining(
147            self.long_average_maturity_time(),
148            current_block_timestamp,
149        );
150        let short_average_time_remaining = self.calculate_scaled_normalized_time_remaining(
151            self.short_average_maturity_time(),
152            current_block_timestamp,
153        );
154        let net_curve_trade = self
155            .calculate_net_curve_trade(long_average_time_remaining, short_average_time_remaining)?;
156        let net_flat_trade = self
157            .calculate_net_flat_trade(long_average_time_remaining, short_average_time_remaining)?;
158
159        let present_value: I256 =
160            I256::try_from(self.share_reserves())? + net_curve_trade + net_flat_trade
161                - I256::try_from(self.minimum_share_reserves())?;
162
163        if present_value < int256!(0) {
164            return Err(eyre!("Negative present value!"));
165        }
166
167        Ok(present_value.try_into()?)
168    }
169
170    pub fn calculate_net_curve_trade(
171        &self,
172        long_average_time_remaining: FixedPoint<U256>,
173        short_average_time_remaining: FixedPoint<U256>,
174    ) -> Result<I256> {
175        // NOTE: To underestimate the impact of closing the net curve position,
176        // we round up the long side of the net curve position (since this
177        // results in a larger value removed from the share reserves) and round
178        // down the short side of the net curve position (since this results in
179        // a smaller value added to the share reserves).
180        //
181        // The net curve position is the net of the longs and shorts that are
182        // currently tradeable on the curve. Given the amount of outstanding
183        // longs `y_l` and shorts `y_s` as well as the average time remaining
184        // of outstanding longs `t_l` and shorts `t_s`, we can
185        // compute the net curve position as:
186        //
187        // netCurveTrade = y_l * t_l - y_s * t_s.
188        let net_curve_position =
189            I256::try_from(self.longs_outstanding().mul_up(long_average_time_remaining))?
190                - I256::try_from(
191                    self.shorts_outstanding()
192                        .mul_down(short_average_time_remaining),
193                )?;
194        match self.effective_share_reserves() {
195            Ok(_) => {}
196            // NOTE: Return 0 to indicate that the net curve trade couldn't be
197            // computed.
198            Err(_) => return Ok(I256::zero()),
199        }
200
201        // If the net curve position is positive, then the pool is net long.
202        // Closing the net curve position results in the longs being paid out
203        // from the share reserves, so we negate the result.
204        match net_curve_position.cmp(&int256!(0)) {
205            Ordering::Greater => {
206                let net_curve_position: FixedPoint<U256> =
207                    FixedPoint::try_from(net_curve_position)?;
208                let max_curve_trade =
209                    match self.calculate_max_sell_bonds_in(self.minimum_share_reserves()) {
210                        Ok(max_curve_trade) => max_curve_trade,
211                        Err(_) => {
212                            // NOTE: Return 0 to indicate that the net curve trade couldn't
213                            // be computed.
214                            return Ok(I256::zero());
215                        }
216                    };
217
218                if max_curve_trade >= net_curve_position {
219                    match self.calculate_shares_out_given_bonds_in_down(net_curve_position) {
220                        Ok(net_curve_trade) => Ok(-I256::try_from(net_curve_trade)?),
221                        Err(err) => {
222                            // If the net curve position is smaller than the
223                            // minimum transaction amount and the trade fails,
224                            // we mark it to 0. This prevents liveness problems
225                            // when the net curve position is very small.
226                            if net_curve_position < self.minimum_transaction_amount() {
227                                Ok(I256::zero())
228                            } else {
229                                Err(err)
230                            }
231                        }
232                    }
233                } else {
234                    // If the share adjustment is greater than or equal to zero,
235                    // then the effective share reserves are less than or equal to
236                    // the share reserves. In this case, the maximum amount of
237                    // shares that can be removed from the share reserves is
238                    // `effectiveShareReserves - minimumShareReserves`.
239                    if self.share_adjustment() >= I256::from(0) {
240                        Ok(-I256::try_from(
241                            self.effective_share_reserves()? - self.minimum_share_reserves(),
242                        )?)
243
244                    // Otherwise, the effective share reserves are greater than the
245                    // share reserves. In this case, the maximum amount of shares
246                    // that can be removed from the share reserves is
247                    // `shareReserves - minimumShareReserves`.
248                    } else {
249                        Ok(-I256::try_from(
250                            self.share_reserves() - self.minimum_share_reserves(),
251                        )?)
252                    }
253                }
254            }
255            Ordering::Less => {
256                let net_curve_position: FixedPoint<U256> =
257                    FixedPoint::try_from(-net_curve_position)?;
258                let max_curve_trade = match self.calculate_max_buy_bonds_out() {
259                    Ok(max_curve_trade) => max_curve_trade,
260                    Err(_) => {
261                        // NOTE: Return 0 to indicate that the net curve trade couldn't
262                        // be computed.
263                        return Ok(I256::zero());
264                    }
265                };
266                if max_curve_trade >= net_curve_position {
267                    match self.calculate_shares_in_given_bonds_out_up(net_curve_position) {
268                        Ok(net_curve_trade) => Ok(I256::try_from(net_curve_trade)?),
269                        Err(err) => {
270                            // If the net curve position is smaller than the
271                            // minimum transaction amount and the trade fails,
272                            // we mark it to 0. This prevents liveness problems
273                            // when the net curve position is very small.
274                            if net_curve_position < self.minimum_transaction_amount() {
275                                Ok(I256::zero())
276                            } else {
277                                Err(err)
278                            }
279                        }
280                    }
281                } else {
282                    let max_share_payment = match self.calculate_max_buy_shares_in() {
283                        Ok(max_share_payment) => max_share_payment,
284                        Err(_) => {
285                            // NOTE: Return 0 to indicate that the net curve trade couldn't
286                            // be computed.
287                            return Ok(I256::zero());
288                        }
289                    };
290
291                    // NOTE: We round the difference down to underestimate the
292                    // impact of closing the net curve position.
293                    Ok(I256::try_from(
294                        max_share_payment
295                            + (net_curve_position - max_curve_trade)
296                                .div_down(self.vault_share_price()),
297                    )?)
298                }
299            }
300            Ordering::Equal => Ok(int256!(0)),
301        }
302    }
303
304    /// Calculates the result of closing the net flat position.
305    pub fn calculate_net_flat_trade(
306        &self,
307        long_average_time_remaining: FixedPoint<U256>,
308        short_average_time_remaining: FixedPoint<U256>,
309    ) -> Result<I256> {
310        if self.vault_share_price() == fixed!(0) {
311            return Err(eyre!("Vault share price is zero."));
312        }
313        if short_average_time_remaining > fixed!(1e18) || long_average_time_remaining > fixed!(1e18)
314        {
315            return Err(eyre!("Average time remaining is greater than 1e18."));
316        }
317        // NOTE: In order to underestimate the impact of closing all of the
318        // flat trades, we round the impact of closing the shorts down and round
319        // the impact of closing the longs up.
320        //
321        // Compute the net of the longs and shorts that will be traded flat and
322        // apply this net to the reserves.
323        let net_flat_trade = I256::try_from(self.shorts_outstanding().mul_div_down(
324            fixed!(1e18) - short_average_time_remaining,
325            self.vault_share_price(),
326        ))? - I256::try_from(self.longs_outstanding().mul_div_up(
327            fixed!(1e18) - long_average_time_remaining,
328            self.vault_share_price(),
329        ))?;
330
331        Ok(net_flat_trade)
332    }
333
334    /// Calculates the amount of withdrawal shares that can be redeemed and
335    /// the share proceeds the withdrawal pool should receive given the
336    /// pool's current idle liquidity. We use the following algorithm to
337    /// ensure that the withdrawal pool receives the correct amount of
338    /// shares to (1) preserve the LP share price and (2) pay out as much
339    /// of the idle liquidity as possible to the withdrawal pool:
340    ///
341    /// 1. If `$y_s \cdot t_s <= y_l \cdot t_l$` or
342    ///    `$y_{\text{max\_out}}(I) >= y_s \cdot t_s - y_l \cdot t_l$` ,
343    ///    set `$dz_{\text{max}} = I$` and proceed to step (3).
344    ///    Otherwise, proceed to step (2).
345    /// 2. Solve
346    ///    `$y_{\text{max\_out}}(dz_{\text{max}}) = y_s \cdot t_s - y_l \cdot t_l$`
347    ///    for `$dz_{\text{max}}$` using Newton's method.
348    /// 3. Set `$dw = (1 - \tfrac{PV(dz_{\text{max}})}{PV(0)}) \cdot l$`.
349    ///    If `$dw <= w$`, then proceed to step (5). Otherwise, set `$dw = w$`
350    ///    and continue to step (4).
351    /// 4. Solve `$\tfrac{PV(0)}{l} = \tfrac{PV(dz)}{(l - dw)}$` for `$dz$`
352    ///    using Newton's method if `$y_l \cdot t_l ~= y_s \cdot t_s$` or
353    ///    directly otherwise.
354    /// 5. Return `$dw$` and `$dz$`.
355    ///
356    ///  Returns `(withdrawal_shares_redeemed, share_proceeds, success)`
357    pub fn calculate_distribute_excess_idle(
358        &self,
359        current_block_timestamp: U256,
360        active_lp_total_supply: FixedPoint<U256>,
361        withdrawal_shares_total_supply: FixedPoint<U256>, // withdraw shares - ready to withdraw
362        max_iterations: u64,
363    ) -> Result<(FixedPoint<U256>, FixedPoint<U256>)> {
364        // Steps 1 and 2: Calculate the maximum amount the share reserves can be
365        // debited. If the effective share reserves or the maximum share
366        // reserves delta can't be calculated or if the maximum share reserves
367        // delta is zero, idle can't be distributed.
368        let success = match self.effective_share_reserves() {
369            Ok(_) => true,
370            // The error is safe from the calculation, panics are not.
371            Err(_) => false,
372        };
373        if success == false {
374            return Ok((fixed!(0), fixed!(0)));
375        }
376        let (max_share_reserves_delta, success) =
377            self.calculate_max_share_reserves_delta_safe(current_block_timestamp)?;
378        if success == false || max_share_reserves_delta == fixed!(0) {
379            return Ok((fixed!(0), fixed!(0)));
380        }
381
382        // Step 3: Calculate the amount of withdrawal shares that can be
383        // redeemed given the maximum share reserves delta.  Otherwise, we
384        // proceed to calculating the amount of shares that should be paid out
385        // to redeem all of the withdrawal shares.
386        let withdrawal_shares_redeemed = {
387            let withdrawal_shares_redeemed = self
388                .calculate_distribute_excess_idle_withdrawal_shares_redeemed(
389                    current_block_timestamp,
390                    max_share_reserves_delta,
391                    active_lp_total_supply,
392                    withdrawal_shares_total_supply,
393                )?;
394
395            // Step 3: If none of the withdrawal shares could be redeemed, then
396            // we're done and we pay out nothing.
397            if withdrawal_shares_redeemed == fixed!(0) {
398                return Ok((fixed!(0), fixed!(0)));
399            }
400            // Step 3: Otherwise if this amount is less than or equal to the amount
401            // of withdrawal shares outstanding, then we're done and we pay out the
402            // full maximum share reserves delta.
403            else if withdrawal_shares_redeemed <= withdrawal_shares_total_supply {
404                return Ok((withdrawal_shares_redeemed, max_share_reserves_delta));
405            }
406            // Step 3: Otherwise, all of the withdrawal shares are redeemed, and we
407            // need to calculate the amount of shares the withdrawal pool should
408            // receive.
409            else {
410                withdrawal_shares_total_supply
411            }
412        };
413
414        // Step 4: Solve for the share proceeds that hold the LP share price
415        // invariant after all of the withdrawal shares are redeemed. If the
416        // calculation returns a share proceeds of zero, we can't pay out
417        // anything.
418        let share_proceeds = self.calculate_distribute_excess_idle_share_proceeds(
419            current_block_timestamp,
420            active_lp_total_supply,
421            withdrawal_shares_total_supply,
422            max_share_reserves_delta,
423            max_iterations,
424        )?;
425        if share_proceeds == fixed!(0) {
426            return Ok((fixed!(0), fixed!(0)));
427        }
428
429        // Step 4: If the share proceeds are greater than or equal to the
430        // maximum share reserves delta that was previously calculated, then
431        // we can't distribute excess idle since we ruled out the possibility
432        // of paying out the full maximum share reserves delta in step 3.
433        if share_proceeds >= max_share_reserves_delta {
434            return Ok((fixed!(0), fixed!(0)));
435        }
436
437        // Step 5: Return the amount of withdrawal shares redeemed and the
438        // share proceeds.
439        Ok((withdrawal_shares_redeemed, share_proceeds))
440    }
441
442    /// Calculates the amount of withdrawal shares that can be redeemed
443    /// given an amount of shares to remove from the share reserves.
444    /// Assuming that dz is the amount of shares to remove from the
445    /// reserves and dl is the amount of LP shares to be burned, we can
446    /// derive the calculation as follows:
447    ///
448    ///  PV(0) / l = PV(dz) / (l - dl)
449    ///                =>
450    ///  dl = l - l * (PV(dz) / PV(0))
451    ///
452    ///  We round this calculation up to err on the side of slightly too
453    ///  many withdrawal shares being redeemed.
454    fn calculate_distribute_excess_idle_withdrawal_shares_redeemed(
455        &self,
456        current_block_timestamp: U256,
457        share_reserves_delta: FixedPoint<U256>,
458        active_lp_total_supply: FixedPoint<U256>,
459        withdrawal_shares_total_supply: FixedPoint<U256>,
460    ) -> Result<FixedPoint<U256>> {
461        // Calculate the present value after debiting the share reserves delta.
462        let updated_state =
463            match self.get_state_after_liquidity_update(-I256::try_from(share_reserves_delta)?) {
464                Ok(state) => state,
465                // NOTE: Return zero to indicate that the withdrawal shares redeemed
466                // couldn't be calculated.
467                Err(_) => return Ok(fixed!(0)),
468            };
469        let starting_present_value = match self.calculate_present_value(current_block_timestamp) {
470            Ok(present_value) => present_value,
471            // NOTE: Return zero to indicate that the withdrawal shares redeemed
472            // couldn't be calculated.
473            // Errors are safe from this calculation, panics are not.
474            Err(_) => return Ok(fixed!(0)),
475        };
476        let ending_present_value =
477            match updated_state.calculate_present_value(current_block_timestamp) {
478                Ok(present_value) => present_value,
479                // NOTE: Return zero to indicate that the withdrawal shares redeemed
480                // couldn't be calculated.
481                // Errors are safe from this calculation, panics are not.
482                Err(_) => return Ok(fixed!(0)),
483            };
484
485        // If the ending present value is greater than or equal to the starting
486        // present value, we short-circuit to avoid distributing excess idle.
487        // This edge-case can occur when the share reserves is very close to the
488        // minimum share reserves with a large value of k.
489        if ending_present_value >= starting_present_value {
490            return Ok(fixed!(0));
491        }
492
493        // NOTE: This subtraction is safe since the ending present value is less
494        // than the starting present value and the rhs is rounded down.
495        //
496        // Calculate the amount of withdrawal shares that can be redeemed.
497        let lp_total_supply = active_lp_total_supply + withdrawal_shares_total_supply;
498        Ok(lp_total_supply
499            - lp_total_supply.mul_div_down(ending_present_value, starting_present_value))
500    }
501
502    /// Calculates the share proceeds to distribute to the withdrawal pool
503    /// assuming that all of the outstanding withdrawal shares will be
504    /// redeemed. The share proceeds are calculated such that the LP share
505    /// price is conserved. When we need to round, we round down to err on
506    /// the side of slightly too few shares being paid out.
507    fn calculate_distribute_excess_idle_share_proceeds(
508        &self,
509        current_block_timestamp: U256,
510        active_lp_total_supply: FixedPoint<U256>, // just the number of lp tokens
511        withdrawal_shares_total_supply: FixedPoint<U256>,
512        max_share_reserves_delta: FixedPoint<U256>,
513        max_iterations: u64,
514    ) -> Result<FixedPoint<U256>> {
515        // Calculate the LP total supply.
516        let lp_total_supply = active_lp_total_supply + withdrawal_shares_total_supply;
517
518        // NOTE: Round the initial guess down to avoid overshooting.
519        //
520        // We make an initial guess for Newton's method by assuming that the
521        // ratio of the share reserves delta to the withdrawal shares
522        // outstanding is equal to the LP share price. In reality, the
523        // withdrawal pool should receive more than this, but it's a good
524        // starting point. The calculation is:
525        //
526        // x_0 = w * (PV(0) / l)
527        let starting_present_value = self.calculate_present_value(current_block_timestamp)?;
528        let mut share_proceeds =
529            withdrawal_shares_total_supply.mul_div_down(starting_present_value, lp_total_supply);
530
531        // If the pool is net neutral, the initial guess is equal to the final
532        // result.
533        let net_curve_trade =
534            self.calculate_net_curve_trade_from_timestamp(current_block_timestamp)?;
535        if net_curve_trade == int256!(0) {
536            return Ok(share_proceeds);
537        }
538
539        // Proceed with Newton's method. The objective function, `F(x)`, is
540        // given by:
541        //
542        // F(x) = PV(x) * l - PV(0) * (l - w)
543        //
544        // Newton's method will terminate as soon as the current iteration is
545        // within the minimum tolerance or the maximum number of iterations has
546        // been reached.
547        let mut smallest_delta = I256::zero();
548        let mut closest_share_proceeds = fixed!(0);
549        let mut closest_present_value = fixed!(0);
550        for _ in 0..max(max_iterations, SHARE_PROCEEDS_MAX_ITERATIONS) {
551            // Clamp the share proceeds to the max share reserves delta since
552            // values above this threshold are always invalid.
553            share_proceeds = min(share_proceeds, max_share_reserves_delta);
554
555            // Simulate applying the share proceeds to the reserves.
556            //
557            // NOTE: We are calling this with 'self' so that original values are
558            // used with the updated value of share_proceeds
559            let updated_state =
560                match self.get_state_after_liquidity_update(-I256::try_from(share_proceeds)?) {
561                    Ok(state) => state,
562                    // NOTE: If the updated reserves can't be calculated,  we can't
563                    // continue the calculation. Return 0 to indicate that the share
564                    // proceeds couldn't be calculated.
565                    // Errors are safe from this calculation, panics are not.
566                    Err(_) => return Ok(fixed!(0)),
567                };
568
569            // Recalculate the present value.
570            let present_value = match updated_state.calculate_present_value(current_block_timestamp)
571            {
572                Ok(present_value) => present_value,
573                // NOTE: If the present value can't be calculated,  we can't
574                // continue the calculation. Return 0 to indicate that the share
575                // proceeds couldn't be calculated.
576                // Errors are safe from this calculation, panics are not.
577                Err(_) => return Ok(fixed!(0)),
578            };
579
580            // Short-circuit if we are within the minimum tolerance.
581            if self.should_short_circuit_distribute_excess_idle_share_proceeds(
582                active_lp_total_supply,
583                starting_present_value,
584                lp_total_supply,
585                present_value,
586            )? {
587                return Ok(share_proceeds);
588            }
589
590            // If the pool is net long, we can solve for the next iteration of
591            // Newton's method directly when the net curve trade is greater than
592            // or equal to the max bond amount.
593            if net_curve_trade > I256::zero() {
594                // Calculate the max bond amount. If the calculation fails, we
595                // return a failure flag.
596                let max_bond_amount = match updated_state
597                    .calculate_max_sell_bonds_in(self.minimum_share_reserves())
598                {
599                    Ok(max_bond_amount) => max_bond_amount,
600                    // NOTE: If the max bond amount couldn't be calculated, we
601                    // can't continue the calculation. Return 0 to indicate that
602                    // the share proceeds couldn't be calculated.
603                    // Errors are safe from this calculation, panics are not.
604                    Err(_) => return Ok(fixed!(0)),
605                };
606
607                // If the net curve trade is greater than or equal to the max
608                // bond amount, we can solve directly for the share proceeds.
609                let net_curve_trade = FixedPoint::from(U256::try_from(net_curve_trade)?);
610                if net_curve_trade >= max_bond_amount {
611                    // Solve the objective function directly assuming that it is
612                    // linear with respect to the share proceeds.
613
614                    let (share_proceeds, success) = updated_state
615                        .calculate_distribute_excess_idle_share_proceeds_net_long_edge_case_safe(
616                            current_block_timestamp,
617                            self.share_adjustment(),
618                            self.share_reserves(),
619                            starting_present_value,
620                            active_lp_total_supply,
621                            withdrawal_shares_total_supply,
622                        )?;
623                    if success == false {
624                        // NOTE: Return 0 to indicate that the share proceeds
625                        // couldn't be calculated.
626                        return Ok(share_proceeds);
627                    }
628
629                    // Simulate applying the share proceeds to the reserves and
630                    // recalculate the max bond amount.
631                    //
632                    // NOTE: We are calling this with 'self' so that original
633                    // values are used with the updated value of share_proceeds.
634                    let updated_state = match self
635                        .get_state_after_liquidity_update(-I256::try_from(share_proceeds)?)
636                    {
637                        Ok(state) => state,
638                        // NOTE: Return 0 to indicate that the share proceeds
639                        // couldn't be calculated.
640                        // Errors are safe from this calculation, panics are not.
641                        Err(_) => return Ok(fixed!(0)),
642                    };
643                    let max_bond_amount = match updated_state
644                        .calculate_max_sell_bonds_in(self.minimum_share_reserves())
645                    {
646                        Ok(max_bond_amount) => max_bond_amount,
647                        // NOTE: Return 0 to indicate that the share proceeds
648                        // couldn't be calculated.
649                        // Errors are safe from this calculation, panics are not.
650                        Err(_) => return Ok(fixed!(0)),
651                    };
652
653                    // If the max bond amount is less than or equal to the net
654                    // curve trade, then Newton's method has terminated since
655                    // proceeding to the next step would result in reaching the
656                    // same point.
657                    if max_bond_amount <= net_curve_trade {
658                        return Ok(share_proceeds);
659                    }
660                    // Otherwise, we continue to the next iteration of Newton's
661                    // method.
662                    else {
663                        continue;
664                    }
665                }
666            }
667
668            // We calculate the derivative of F(x) using the derivative of
669            // `calculateSharesOutGivenBondsIn` when the pool is net long or
670            // the derivative of `calculateSharesInGivenBondsOut`. when the pool
671            // is net short.
672            let derivative = match updated_state
673                .calculate_shares_delta_given_bonds_delta_derivative(
674                    net_curve_trade,
675                    self.share_reserves(),
676                    self.bond_reserves(),
677                    self.effective_share_reserves()?,
678                    self.share_adjustment(),
679                ) {
680                Ok(derivative) => derivative,
681                // NOTE: Return 0 to indicate that the share proceeds
682                // couldn't be calculated.
683                // Errors are safe from this calculation, panics are not.
684                Err(_) => return Ok(fixed!(0)),
685            };
686            if derivative >= fixed!(1e18) {
687                return Ok(fixed!(0));
688            }
689            let derivative = fixed!(1e18) - derivative;
690
691            // NOTE: Round the delta down to avoid overshooting.
692            //
693            // Calculate the objective function's value. If the value's magnitude
694            // is smaller than the previous smallest value, then we update the
695            // value and record the share proceeds. We'll ultimately return the
696            // share proceeds that resulted in the smallest value.
697            let delta = I256::try_from(present_value.mul_down(lp_total_supply))?
698                - I256::try_from(starting_present_value.mul_up(active_lp_total_supply))?;
699            if smallest_delta == I256::from(0) || delta.abs() < smallest_delta.abs() {
700                smallest_delta = delta;
701                closest_share_proceeds = share_proceeds;
702                closest_present_value = present_value;
703            }
704
705            // We calculate the updated share proceeds `x_n+1` by proceeding
706            // with Newton's method. This is given by:
707            //
708            // x_n+1 = x_n - F(x_n) / F'(x_n)
709            if delta > I256::zero() {
710                // NOTE: Round the quotient down to avoid overshooting.
711                share_proceeds = share_proceeds
712                    + FixedPoint::try_from(delta)?
713                        .div_down(derivative)
714                        .div_down(lp_total_supply);
715            } else if delta < I256::zero() {
716                let delta = FixedPoint::try_from(-delta)?
717                    .div_down(derivative)
718                    .div_down(lp_total_supply);
719                if delta < share_proceeds {
720                    share_proceeds = share_proceeds - delta;
721                } else {
722                    // NOTE: Returning 0 to indicate that the share proceeds
723                    // couldn't be calculated.
724                    return Ok(fixed!(0));
725                }
726            } else {
727                break;
728            }
729        }
730
731        // Get the updated present value after applying the share proceeds.
732        let updated_state =
733            match self.get_state_after_liquidity_update(-I256::try_from(share_proceeds)?) {
734                Ok(state) => state,
735                // NOTE: Return 0 to indicate that the share proceeds couldn't
736                // be calculated.
737                // Errors are safe from this calculation, panics are not.
738                Err(_) => return Ok(fixed!(0)),
739            };
740        let present_value = updated_state.calculate_present_value(current_block_timestamp)?;
741
742        // Check to see if the current share proceeds is closer to the optimal
743        // value than the previous closest value. We'll choose whichever of the
744        // share proceeds that is closer to the optimal value.
745        let last_delta = I256::try_from(present_value.mul_down(lp_total_supply))?
746            - I256::try_from(starting_present_value.mul_up(active_lp_total_supply))?;
747        if last_delta.abs() < smallest_delta.abs() {
748            closest_share_proceeds = share_proceeds;
749            closest_present_value = present_value;
750        }
751
752        // Verify that the LP share price was conserved within a reasonable
753        // tolerance.
754        if
755        // NOTE: Round down to make the check stricter.
756        closest_present_value.div_down(active_lp_total_supply)
757                < starting_present_value.mul_div_up(
758                    fixed!(1e18) - FixedPoint::from(SHARE_PROCEEDS_TOLERANCE),
759                    lp_total_supply,
760                ) ||
761            // NOTE: Round up to make the check stricter.
762            closest_present_value.div_up(active_lp_total_supply)
763                > starting_present_value.mul_div_down(
764                    fixed!(1e18) + FixedPoint::from(SHARE_PROCEEDS_TOLERANCE),
765                    lp_total_supply,
766                )
767        {
768            return Err(eyre!("LP share price was not conserved within tolerance."));
769        }
770
771        Ok(closest_share_proceeds)
772    }
773
774    /// One of the edge cases that occurs when using Newton's method for
775    /// the share proceeds while distributing excess idle is when the net
776    /// curve trade is larger than the max bond amount. In this case, the
777    /// the present value simplifies to the following:
778    ///
779    /// PV(dz) = (z - dz) + net_c(dz) + net_f - z_min
780    ///        = (z - dz) - z_max_out(dz) + net_f - z_min
781    ///
782    /// There are two cases to evaluate:
783    ///
784    /// (1) zeta > 0:
785    ///
786    /// z_max_out(dz) = ((z - dz) / z) * (z - zeta) - z_min
787    ///
788    /// =>
789    ///
790    /// PV(dz) = zeta * ((z - dz) / z) + net_f
791    ///
792    /// (2) zeta <= 0:
793    ///
794    /// z_max_out(dz) = (z - dz) - z_min
795    ///
796    /// =>
797    ///
798    /// PV(dz) = net_f
799    ///
800    /// Since the present value is constant with respect to the share
801    /// proceeds in case 2, Newton's method has achieved a stationary point
802    /// and can't proceed. On the other hand, the present value is linear
803    /// with respect to the share proceeds, and we can solve for the next
804    /// step of Newton's method directly as follows:
805    ///
806    /// PV(0) / l = PV(dz) / (l - w)
807    ///
808    /// =>
809    ///
810    /// dz = z - ((PV(0) / l) * (l - w) - net_f) / (zeta / z)
811    ///
812    /// We round the share proceeds down to err on the side of the
813    /// withdrawal pool receiving slightly less shares.
814    fn calculate_distribute_excess_idle_share_proceeds_net_long_edge_case_safe(
815        &self,
816        current_block_timestamp: U256,
817        original_share_adjustment: I256,
818        original_share_reserves: FixedPoint<U256>,
819        starting_present_value: FixedPoint<U256>,
820        active_lp_total_supply: FixedPoint<U256>,
821        withdrawal_shares_total_supply: FixedPoint<U256>,
822    ) -> Result<(FixedPoint<U256>, bool)> {
823        // If the original share adjustment is zero or negative, we cannot
824        // calculate the share proceeds. This should never happen, but for
825        // safety we return a failure flag and break the loop at this point.
826        if original_share_adjustment <= I256::zero() {
827            return Ok((fixed!(0), false));
828        }
829        let original_share_adjustment: U256 = original_share_adjustment.abs().try_into().unwrap();
830
831        // Calculate the net flat trade.
832        let net_flat_trade =
833            self.calculate_net_flat_trade_from_timestamp(current_block_timestamp)?;
834
835        // Avoid panic: make sure we don't divide by zero before calculating the rhs.
836        if active_lp_total_supply + withdrawal_shares_total_supply == fixed!(0) {
837            return Err(eyre!(
838                "active_lp_total_supply + withdrawal_shares_total_supply is zero"
839            ));
840        }
841
842        // NOTE: Round up since this is the rhs of the final subtraction.
843        //
844        // rhs = (PV(0) / l) * (l - w) - net_f
845        let rhs = {
846            let rhs: FixedPoint<U256> = starting_present_value.mul_div_up(
847                active_lp_total_supply,
848                active_lp_total_supply + withdrawal_shares_total_supply,
849            );
850            if net_flat_trade >= I256::zero() {
851                if net_flat_trade < I256::try_from(rhs)? {
852                    rhs - net_flat_trade.try_into()?
853                } else {
854                    // NOTE: Return a failure flag if computing the rhs would
855                    // underflow.
856                    return Ok((fixed!(0), false));
857                }
858            } else {
859                rhs + (-net_flat_trade).try_into()?
860            }
861        };
862
863        // NOTE: Round up since this is the rhs of the final subtraction.
864        //
865        // rhs = ((PV(0) / l) * (l - w) - net_f) / (zeta / z)
866        let rhs =
867            original_share_reserves.mul_div_up(rhs, FixedPoint::from(original_share_adjustment));
868
869        // share proceeds = z - rhs
870        if original_share_reserves < rhs {
871            return Ok((fixed!(0), false));
872        }
873
874        Ok((original_share_reserves - rhs, true))
875    }
876
877    /// Checks to see if we should short-circuit the iterative calculation of
878    /// the share proceeds when distributing excess idle liquidity. This
879    /// verifies that the ending LP share price is greater than or equal to the
880    /// starting LP share price and less than or equal to the starting LP share
881    /// price plus the minimum tolerance.
882    fn should_short_circuit_distribute_excess_idle_share_proceeds(
883        &self,
884        active_lp_total_supply: FixedPoint<U256>, // just the total number of lp shares
885        starting_present_value: FixedPoint<U256>,
886        lp_total_supply: FixedPoint<U256>, // total lp shares and withdrawal shares - w.s. ready to withraw
887        present_value: FixedPoint<U256>,
888    ) -> Result<bool> {
889        Ok(
890            // Ensure that new LP share price is greater than or equal to the
891            // previous LP share price:
892            //
893            // PV_1 / l_1 >= PV_0 / l_0
894            //
895            // NOTE: Round the LHS down to make the check stricter.
896            present_value.div_down(active_lp_total_supply) >=
897            starting_present_value.div_up(lp_total_supply)
898            // Ensure that new LP share price is less than or equal to the
899            // previous LP share price plus the minimum tolerance:
900            //
901            // PV_1 / l_1 <= (PV_0 / l_0) * (1 + tolerance)
902            //
903            // NOTE: Round the LHS up to make the check stricter.
904            && present_value.div_up(active_lp_total_supply) <=
905            (fixed!(1e18) + FixedPoint::from(SHARE_PROCEEDS_SHORT_CIRCUIT_TOLERANCE)).mul_div_down(
906                starting_present_value,
907                lp_total_supply),
908        )
909    }
910
911    /// Calculates the upper bound on the share proceeds of distributing
912    /// excess idle. When the pool is net long or net neutral, the upper
913    /// bound is the amount of idle liquidity. When the pool is net short,
914    /// the upper bound is the share reserves delta that results in the
915    /// maximum amount of bonds that can be purchased being equal to the
916    /// net short position.
917    fn calculate_max_share_reserves_delta_safe(
918        &self,
919        current_block_timestamp: U256,
920    ) -> Result<(FixedPoint<U256>, bool)> {
921        let net_curve_trade =
922            self.calculate_net_curve_trade_from_timestamp(current_block_timestamp)?;
923        let idle = self.calculate_idle_share_reserves();
924        // If the net curve position is zero or net long, then the maximum
925        // share reserves delta is equal to the pool's idle.
926        if net_curve_trade >= I256::from(0) {
927            return Ok((idle, true));
928        }
929        let net_curve_trade = FixedPoint::try_from(-net_curve_trade)?;
930
931        // Calculate the max bond amount. if the calculation fails, we return a
932        // failure flag. if the calculation succeeds but the max bond amount
933        // is zero, then we return a failure flag since we can't divide by zero.
934        let max_bond_amount = match self.calculate_max_buy_bonds_out() {
935            Ok(result) => result,
936            // Errors are safe from the calculation, panics are not.
937            Err(_) => fixed!(0),
938        };
939        if max_bond_amount == fixed!(0) {
940            return Ok((fixed!(0), false));
941        }
942
943        // We can solve for the maximum share reserves delta in one shot using
944        // the fact that the maximum amount of bonds that can be purchased is
945        // linear with respect to the scaling factor applied to the reserves.
946        // In other words, if s > 0 is a factor scaling the reserves, we have
947        // the following relationship:
948        //
949        // y_out^max(s * z, s * y, s * zeta) = s * y_out^max(z, y, zeta)
950        //
951        // We solve for the maximum share reserves delta by finding the scaling
952        // factor that results in the maximum amount of bonds that can be
953        // purchased being equal to the net curve trade. We can derive this
954        // maximum using the linearity property mentioned above as follows:
955        //
956        // y_out^max(s * z, s * y, s * zeta) - netCurveTrade = 0
957        //                        =>
958        // s * y_out^max(z, y, zeta) - netCurveTrade = 0
959        //                        =>
960        // s = netCurveTrade / y_out^max(z, y, zeta)
961        let max_scaling_factor = net_curve_trade.div_up(max_bond_amount);
962
963        // Using the maximum scaling factor, we can calculate the maximum share
964        // reserves delta as:
965        //
966        // maxShareReservesDelta = z * (1 - s)
967        let max_share_reserves_delta = if max_scaling_factor <= fixed!(1e18) {
968            (fixed!(1e18) - max_scaling_factor).mul_down(self.share_reserves())
969        } else {
970            // NOTE: If the max scaling factor is greater than one, the
971            // calculation fails and we return a failure flag.
972            return Ok((fixed!(0), false));
973        };
974
975        // If the maximum share reserves delta is greater than the idle, then
976        // the maximum share reserves delta is equal to the idle.
977        if max_share_reserves_delta > idle {
978            return Ok((idle, true));
979        }
980        return Ok((max_share_reserves_delta, true));
981    }
982
983    /// TODO: https://github.com/delvtech/hyperdrive/issues/965
984    ///
985    /// Note that the state is the present state of the pool and original values
986    /// passed in as parameters.  Present sate variables are not expressly
987    /// paased in because so that downstream function like kUp() can still be
988    /// used.
989    ///
990    /// Given a signed bond amount, this function calculates the negation
991    /// of the derivative of `calculateSharesOutGivenBondsIn` when the
992    /// bond amount is positive or the derivative of
993    /// `calculateSharesInGivenBondsOut` when the bond amount is negative.
994    /// In both cases, the calculation is given by:
995    ///
996    ///  derivative = (1 - zeta / z) * (
997    ///      1 - (1 / c) * (
998    ///          c * (mu * z_e(x)) ** -t_s +
999    ///          (y / z_e) * y(x) ** -t_s  -
1000    ///          (y / z_e) * (y(x) + dy) ** -t_s
1001    ///      ) * (
1002    ///          (mu / c) * (k(x) - (y(x) + dy) ** (1 - t_s))
1003    ///      ) ** (t_s / (1 - t_s))
1004    ///  )
1005    ///
1006    ///  This quantity is used in Newton's method to search for the optimal
1007    ///  share proceeds. When the pool is net long, We can express the
1008    ///  derivative of the objective function F(x) by the derivative
1009    ///  -z_out'(x) that this function returns:
1010    ///
1011    ///  -F'(x) = l * -PV'(x)
1012    ///         = l * (1 - net_c'(x))
1013    ///         = l * (1 + z_out'(x))
1014    ///         = l * (1 - derivative)
1015    ///
1016    ///  When the pool is net short, we can express the derivative of the
1017    ///  objective function F(x) by the derivative z_in'(x) that this
1018    ///  function returns:
1019    ///
1020    ///  -F'(x) = l * -PV'(x)
1021    ///         = l * (1 - net_c'(x))
1022    ///         = l * (1 - z_in'(x))
1023    ///         = l * (1 - derivative)
1024    ///
1025    ///  With these calculations in mind, this function rounds its result
1026    ///  down so that F'(x) is overestimated. Since F'(x) is in the
1027    ///  denominator of Newton's method, overestimating F'(x) helps to avoid
1028    ///  overshooting the optimal solution.
1029    fn calculate_shares_delta_given_bonds_delta_derivative(
1030        &self,
1031        bond_amount: I256,
1032        original_share_reserves: FixedPoint<U256>,
1033        original_bond_reserves: FixedPoint<U256>,
1034        original_effective_share_reserves: FixedPoint<U256>,
1035        original_share_adjustment: I256,
1036    ) -> Result<FixedPoint<U256>> {
1037        // Calculate the bond reserves after the bond amount is applied.
1038        let bond_reserves_after = if bond_amount >= I256::zero() {
1039            self.bond_reserves() + bond_amount.try_into()?
1040        } else {
1041            let bond_amount = FixedPoint::from(U256::try_from(-bond_amount)?);
1042            if bond_amount < self.bond_reserves() {
1043                self.bond_reserves() - bond_amount
1044            } else {
1045                return Err(eyre!("Calculating the bond reserves underflows"));
1046            }
1047        };
1048
1049        // NOTE: Round up since this is on the rhs of the final subtraction.
1050        //
1051        // derivative = c * (mu * z_e(x)) ** -t_s +
1052        //              (y / z_e) * (y(x)) ** -t_s -
1053        //              (y / z_e) * (y(x) + dy) ** -t_s
1054        let effective_share_reserves = self.effective_share_reserves()?;
1055        // NOTE: The exponent is positive and base is flipped to handle the negative value.
1056        let derivative = self.vault_share_price().div_up(
1057            self.initial_vault_share_price()
1058                .mul_down(effective_share_reserves)
1059                .pow(self.time_stretch())?,
1060        ) + original_bond_reserves.div_up(
1061            original_effective_share_reserves
1062                .mul_down(self.bond_reserves().pow(self.time_stretch())?),
1063        );
1064
1065        // NOTE: Rounding this down rounds the subtraction up.
1066        let rhs = original_bond_reserves.div_down(
1067            original_effective_share_reserves.mul_up(bond_reserves_after.pow(self.time_stretch())?),
1068        );
1069        if derivative < rhs {
1070            return Err(eyre!("Derivative is less than right hand side"));
1071        }
1072        let derivative = derivative - rhs;
1073
1074        // NOTE: Round up since this is on the rhs of the final subtraction.
1075        //
1076        // inner = (
1077        //             (mu / c) * (k(x) - (y(x) + dy) ** (1 - t_s))
1078        //         ) ** (t_s / (1 - t_s))
1079        let k = self.k_up()?;
1080        let inner = bond_reserves_after.pow(fixed!(1e18) - self.time_stretch())?;
1081        if k < inner {
1082            return Err(eyre!("k is less than inner"));
1083        }
1084        let inner = k - inner;
1085        let inner = inner.mul_div_up(self.initial_vault_share_price(), self.vault_share_price());
1086        let inner = if inner >= fixed!(1e18) {
1087            // NOTE: Round the exponent up since this rounds the result up.
1088            inner.pow(
1089                self.time_stretch()
1090                    .div_up(fixed!(1e18) - self.time_stretch()),
1091            )?
1092        } else {
1093            // NOTE: Round the exponent down since this rounds the result up.
1094            inner.pow(
1095                self.time_stretch()
1096                    .div_down(fixed!(1e18) - self.time_stretch()),
1097            )?
1098        };
1099        let derivative = derivative.mul_div_up(inner, self.vault_share_price());
1100        let derivative = if fixed!(1e18) > derivative {
1101            fixed!(1e18) - derivative
1102        } else {
1103            // NOTE: Small rounding errors can result in the derivative being
1104            // slightly (on the order of a few wei) greater than 1. In this case,
1105            // we return 0 since we should proceed with Newton's method.
1106            return Ok(fixed!(0));
1107        };
1108        // NOTE: Round down to round the final result down.
1109        //
1110        // derivative = derivative * (1 - (zeta / z))
1111        let derivative = if original_share_adjustment >= I256::zero() {
1112            let right_hand_side =
1113                FixedPoint::try_from(original_share_adjustment)?.div_up(original_share_reserves);
1114            if right_hand_side > fixed!(1e18) {
1115                return Err(eyre!("Right hand side is greater than 1e18"));
1116            }
1117            let right_hand_side = fixed!(1e18) - right_hand_side;
1118            derivative.mul_down(right_hand_side)
1119        } else {
1120            derivative.mul_down(
1121                fixed!(1e18)
1122                    + FixedPoint::try_from(-original_share_adjustment)?
1123                        .div_down(original_share_reserves),
1124            )
1125        };
1126
1127        Ok(derivative)
1128    }
1129}
1130
1131#[cfg(test)]
1132mod tests {
1133    use fixedpointmath::{fixed_i256, uint256};
1134    use hyperdrive_test_utils::{
1135        chain::TestChain,
1136        constants::{FAST_FUZZ_RUNS, FUZZ_RUNS},
1137    };
1138    use hyperdrive_wrappers::wrappers::mock_lp_math::{
1139        DistributeExcessIdleParams, PresentValueParams,
1140    };
1141    use rand::{thread_rng, Rng};
1142
1143    use super::*;
1144
1145    #[tokio::test]
1146    async fn fuzz_calculate_initial_reserves() -> Result<()> {
1147        let chain = TestChain::new().await?;
1148
1149        // Fuzz the rust and solidity implementations against each other.
1150        let mut rng = thread_rng();
1151        for _ in 0..*FAST_FUZZ_RUNS {
1152            let state = rng.gen::<State>();
1153            let initial_contribution = rng.gen_range(fixed!(0)..=state.bond_reserves());
1154            let initial_rate = rng.gen_range(fixed!(0)..=fixed!(1));
1155            let (actual_share_reserves, actual_share_adjustment, actual_bond_reserves) =
1156                state.calculate_initial_reserves(initial_contribution, initial_rate)?;
1157            match chain
1158                .mock_lp_math()
1159                .calculate_initial_reserves(
1160                    initial_contribution.into(),
1161                    state.vault_share_price().into(),
1162                    state.initial_vault_share_price().into(),
1163                    initial_rate.into(),
1164                    state.position_duration().into(),
1165                    state.time_stretch().into(),
1166                )
1167                .call()
1168                .await
1169            {
1170                Ok(expected) => {
1171                    assert_eq!(actual_share_reserves, expected.0.into());
1172                    assert_eq!(actual_share_adjustment, expected.1);
1173                    assert_eq!(actual_bond_reserves, expected.2.into());
1174                }
1175                Err(_) => {}
1176            }
1177        }
1178
1179        Ok(())
1180    }
1181
1182    #[tokio::test]
1183    async fn fuzz_calculate_present_value() -> Result<()> {
1184        let chain = TestChain::new().await?;
1185
1186        // Fuzz the rust and solidity implementations against each other.
1187        let mut rng = thread_rng();
1188        for _ in 0..*FAST_FUZZ_RUNS {
1189            let state = rng.gen::<State>();
1190            let current_block_timestamp = rng.gen_range(fixed!(1)..=fixed!(1e4));
1191            let actual = state.calculate_present_value(current_block_timestamp.into());
1192            match chain
1193                .mock_lp_math()
1194                .calculate_present_value(PresentValueParams {
1195                    share_reserves: state.info.share_reserves,
1196                    bond_reserves: state.info.bond_reserves,
1197                    longs_outstanding: state.info.longs_outstanding,
1198                    share_adjustment: state.info.share_adjustment,
1199                    time_stretch: state.config.time_stretch,
1200                    vault_share_price: state.info.vault_share_price,
1201                    initial_vault_share_price: state.config.initial_vault_share_price,
1202                    minimum_share_reserves: state.config.minimum_share_reserves,
1203                    minimum_transaction_amount: state.config.minimum_transaction_amount,
1204                    long_average_time_remaining: state
1205                        .calculate_scaled_normalized_time_remaining(
1206                            state.long_average_maturity_time(),
1207                            current_block_timestamp.into(),
1208                        )
1209                        .into(),
1210                    short_average_time_remaining: state
1211                        .calculate_scaled_normalized_time_remaining(
1212                            state.short_average_maturity_time(),
1213                            current_block_timestamp.into(),
1214                        )
1215                        .into(),
1216                    shorts_outstanding: state.shorts_outstanding().into(),
1217                })
1218                .call()
1219                .await
1220            {
1221                Ok(expected) => {
1222                    assert_eq!(actual.unwrap(), FixedPoint::from(expected));
1223                }
1224                Err(_) => assert!(actual.is_err()),
1225            }
1226        }
1227
1228        Ok(())
1229    }
1230
1231    #[tokio::test]
1232    async fn fuzz_calculate_net_curve_trade_safe() -> Result<()> {
1233        let chain = TestChain::new().await?;
1234
1235        // Fuzz the rust and solidity implementations against each other.
1236        let mut rng = thread_rng();
1237        for _ in 0..*FAST_FUZZ_RUNS {
1238            let state = rng.gen::<State>();
1239            let current_block_timestamp = rng.gen_range(fixed!(1)..=fixed!(1e4));
1240            let long_average_time_remaining = state.calculate_normalized_time_remaining(
1241                state.long_average_maturity_time().into(),
1242                current_block_timestamp.into(),
1243            );
1244            let short_average_time_remaining = state.calculate_normalized_time_remaining(
1245                state.short_average_maturity_time().into(),
1246                current_block_timestamp.into(),
1247            );
1248            let actual = state.calculate_net_curve_trade(
1249                long_average_time_remaining,
1250                short_average_time_remaining,
1251            );
1252            match chain
1253                .mock_lp_math()
1254                .calculate_net_curve_trade(PresentValueParams {
1255                    share_reserves: state.info.share_reserves,
1256                    bond_reserves: state.info.bond_reserves,
1257                    longs_outstanding: state.info.longs_outstanding,
1258                    share_adjustment: state.info.share_adjustment,
1259                    time_stretch: state.config.time_stretch,
1260                    vault_share_price: state.info.vault_share_price,
1261                    initial_vault_share_price: state.config.initial_vault_share_price,
1262                    minimum_share_reserves: state.config.minimum_share_reserves,
1263                    minimum_transaction_amount: state.config.minimum_transaction_amount,
1264                    long_average_time_remaining: long_average_time_remaining.into(),
1265                    short_average_time_remaining: short_average_time_remaining.into(),
1266                    shorts_outstanding: state.shorts_outstanding().into(),
1267                })
1268                .call()
1269                .await
1270            {
1271                Ok(expected) => {
1272                    assert_eq!(actual.unwrap(), expected);
1273                }
1274                Err(_) => {
1275                    assert!(actual.is_err());
1276                }
1277            }
1278        }
1279
1280        Ok(())
1281    }
1282
1283    #[tokio::test]
1284    async fn fuzz_calculate_net_flat_trade() -> Result<()> {
1285        let chain = TestChain::new().await?;
1286
1287        // Fuzz the rust and solidity implementations against each other.
1288        let mut rng = thread_rng();
1289        for _ in 0..*FAST_FUZZ_RUNS {
1290            let state = rng.gen::<State>();
1291
1292            let current_block_timestamp = rng.gen_range(fixed!(1)..=fixed!(1e4));
1293
1294            let long_average_time_remaining = state.calculate_normalized_time_remaining(
1295                state.long_average_maturity_time().into(),
1296                current_block_timestamp.into(),
1297            );
1298            let short_average_time_remaining = state.calculate_normalized_time_remaining(
1299                state.short_average_maturity_time().into(),
1300                current_block_timestamp.into(),
1301            );
1302            let actual = state.calculate_net_flat_trade(
1303                long_average_time_remaining,
1304                short_average_time_remaining,
1305            );
1306            match chain
1307                .mock_lp_math()
1308                .calculate_net_flat_trade(PresentValueParams {
1309                    share_reserves: state.info.share_reserves,
1310                    bond_reserves: state.info.bond_reserves,
1311                    longs_outstanding: state.info.longs_outstanding,
1312                    share_adjustment: state.info.share_adjustment,
1313                    time_stretch: state.config.time_stretch,
1314                    vault_share_price: state.info.vault_share_price,
1315                    initial_vault_share_price: state.config.initial_vault_share_price,
1316                    minimum_share_reserves: state.config.minimum_share_reserves,
1317                    minimum_transaction_amount: state.config.minimum_transaction_amount,
1318                    long_average_time_remaining: long_average_time_remaining.into(),
1319                    short_average_time_remaining: short_average_time_remaining.into(),
1320                    shorts_outstanding: state.shorts_outstanding().into(),
1321                })
1322                .call()
1323                .await
1324            {
1325                Ok(expected) => {
1326                    assert_eq!(actual.unwrap(), expected);
1327                }
1328                Err(_) => assert!(actual.is_err()),
1329            }
1330        }
1331
1332        Ok(())
1333    }
1334
1335    #[tokio::test]
1336    async fn fuzz_calculate_distribute_excess_idle() -> Result<()> {
1337        let chain = TestChain::new().await?;
1338        let alice = chain.alice().await?;
1339        let mock = chain.mock_lp_math();
1340
1341        // Fuzz the rust and solidity implementations against each other.
1342        let mut rng = thread_rng();
1343
1344        for _ in 0..*FUZZ_RUNS {
1345            // Generate random states.
1346            let mut present_state = rng.gen::<State>();
1347
1348            // Make sure maturity times are in the future.
1349            let current_block_timestamp = alice.now().await?;
1350            present_state.info.long_average_maturity_time += current_block_timestamp;
1351            present_state.info.short_average_maturity_time += current_block_timestamp;
1352
1353            // active_lp_total_supply and _withdrawal_shares_total_supply are just the token supplies.
1354            // Neither are supplied in the PoolInfo so we need to make them here.  lp_total_supply is defined as:
1355            // lp_total_supply = active_lp_total_supply + withdrawal_shares_total_supply - withdrawal_shares_ready_to_withdraw
1356            // active_lp_total_supply = lp_total_supply - withdrawal_shares_total_supply + withdrawal_shares_ready_to_withdraw
1357            // We clip withdrawal_shares_total_supply to ensure active_lp_total_supply doesn't underflow.
1358            let active_lp_total_supply = present_state.lp_total_supply()
1359                + present_state.withdrawal_shares_ready_to_withdraw();
1360            let withdrawal_shares_total_supply = rng.gen_range(fixed!(0)..=active_lp_total_supply);
1361            let active_lp_total_supply = active_lp_total_supply - withdrawal_shares_total_supply;
1362            // This errors out a lot so we need to catch that here.
1363            let starting_present_value =
1364                match present_state.calculate_present_value(U256::from(current_block_timestamp)) {
1365                    Ok(result) => result,
1366                    Err(_) => continue,
1367                };
1368
1369            // Calculate the result from the Rust implementation.
1370            let actual = present_state.calculate_distribute_excess_idle(
1371                current_block_timestamp,
1372                active_lp_total_supply,
1373                withdrawal_shares_total_supply,
1374                SHARE_PROCEEDS_MAX_ITERATIONS,
1375            );
1376
1377            // To keep precision of long and short average maturity time (from contract call)
1378            // we scale the block timestamp and position duration by 1e18 to calculate
1379            // the normalized time remaining.
1380            let long_average_time_remaining = present_state
1381                .calculate_scaled_normalized_time_remaining(
1382                    present_state.long_average_maturity_time(),
1383                    current_block_timestamp,
1384                );
1385            let short_average_time_remaining = present_state
1386                .calculate_scaled_normalized_time_remaining(
1387                    present_state.short_average_maturity_time(),
1388                    current_block_timestamp,
1389                );
1390            let net_curve_trade = present_state.calculate_net_curve_trade(
1391                long_average_time_remaining,
1392                short_average_time_remaining,
1393            )?;
1394
1395            let params = DistributeExcessIdleParams {
1396                present_value_params: PresentValueParams {
1397                    share_reserves: present_state.info.share_reserves,
1398                    bond_reserves: present_state.info.bond_reserves,
1399                    longs_outstanding: present_state.info.longs_outstanding,
1400                    share_adjustment: present_state.info.share_adjustment,
1401                    time_stretch: present_state.config.time_stretch,
1402                    vault_share_price: present_state.info.vault_share_price,
1403                    initial_vault_share_price: present_state.config.initial_vault_share_price,
1404                    minimum_share_reserves: present_state.config.minimum_share_reserves,
1405                    minimum_transaction_amount: present_state.config.minimum_transaction_amount,
1406                    long_average_time_remaining: long_average_time_remaining.into(),
1407                    short_average_time_remaining: short_average_time_remaining.into(),
1408                    shorts_outstanding: present_state.shorts_outstanding().into(),
1409                },
1410                starting_present_value: starting_present_value.into(),
1411                active_lp_total_supply: active_lp_total_supply.into(),
1412                withdrawal_shares_total_supply: withdrawal_shares_total_supply.into(),
1413                idle: present_state.calculate_idle_share_reserves().into(),
1414                net_curve_trade: net_curve_trade,
1415                original_share_reserves: present_state.share_reserves().into(),
1416                original_share_adjustment: present_state.share_adjustment(),
1417                original_bond_reserves: present_state.bond_reserves().into(),
1418            };
1419
1420            // Make the solidity call and compare to the Rust implementation.
1421            match mock
1422                .calculate_distribute_excess_idle(params, SHARE_PROCEEDS_MAX_ITERATIONS.into())
1423                .call()
1424                .await
1425            {
1426                Ok(expected) => {
1427                    let (sol_withdrawal_shares_redeemed, sol_share_proceeds) = expected;
1428                    let (rust_withdrawal_shares_redeemed, rust_share_proceeds) = actual?;
1429                    assert_eq!(
1430                        sol_withdrawal_shares_redeemed,
1431                        U256::from(rust_withdrawal_shares_redeemed)
1432                    );
1433                    assert_eq!(sol_share_proceeds, U256::from(rust_share_proceeds));
1434                }
1435                Err(_) => {
1436                    assert!(actual.is_err())
1437                }
1438            }
1439        }
1440        Ok(())
1441    }
1442
1443    #[tokio::test]
1444    async fn fuzz_calculate_distribute_excess_idle_withdrawal_shares_redeemed() -> Result<()> {
1445        let chain = TestChain::new().await?;
1446        let alice = chain.alice().await?;
1447        let mock = chain.mock_lp_math();
1448
1449        // Fuzz the rust and solidity implementations against each other.
1450        let mut rng = thread_rng();
1451
1452        for _ in 0..*FAST_FUZZ_RUNS {
1453            // Generate random states.
1454            let mut present_state = rng.gen::<State>();
1455
1456            // Make sure maturity times are in the future.
1457            let current_block_timestamp = alice.now().await?;
1458            present_state.info.long_average_maturity_time += current_block_timestamp;
1459            present_state.info.short_average_maturity_time += current_block_timestamp;
1460
1461            // active_lp_total_supply and _withdrawal_shares_total_supply are just the token supplies.
1462            // Neither are supplied in the PoolInfo so we need to make them here.  lp_total_supply is defined as:
1463            // lp_total_supply = active_lp_total_supply + withdrawal_shares_total_supply - withdrawal_shares_ready_to_withdraw
1464            // active_lp_total_supply = lp_total_supply - withdrawal_shares_total_supply + withdrawal_shares_ready_to_withdraw
1465            // We clip withdrawal_shares_total_supply to ensure active_lp_total_supply doesn't underflow.
1466            let active_lp_total_supply = present_state.lp_total_supply()
1467                + present_state.withdrawal_shares_ready_to_withdraw();
1468            let withdrawal_shares_total_supply = rng.gen_range(fixed!(0)..=active_lp_total_supply);
1469            let active_lp_total_supply = active_lp_total_supply - withdrawal_shares_total_supply;
1470            // This errors out a lot so we need to catch that here.
1471            let starting_present_value =
1472                match present_state.calculate_present_value(U256::from(current_block_timestamp)) {
1473                    Ok(result) => result,
1474                    Err(_) => continue,
1475                };
1476            let (share_reserves_delta, _) =
1477                present_state.calculate_max_share_reserves_delta_safe(current_block_timestamp)?;
1478
1479            // Calculate the result from the Rust implementation.
1480            let actual = present_state.calculate_distribute_excess_idle_withdrawal_shares_redeemed(
1481                current_block_timestamp,
1482                share_reserves_delta,
1483                active_lp_total_supply,
1484                withdrawal_shares_total_supply,
1485            );
1486
1487            // To keep precision of long and short average maturity time (from contract call)
1488            // we scale the block timestamp and position duration by 1e18 to calculate
1489            // the normalized time remaining.
1490            let long_average_time_remaining = present_state
1491                .calculate_scaled_normalized_time_remaining(
1492                    present_state.long_average_maturity_time(),
1493                    current_block_timestamp,
1494                );
1495            let short_average_time_remaining = present_state
1496                .calculate_scaled_normalized_time_remaining(
1497                    present_state.short_average_maturity_time(),
1498                    current_block_timestamp,
1499                );
1500            let net_curve_trade = present_state.calculate_net_curve_trade(
1501                long_average_time_remaining,
1502                short_average_time_remaining,
1503            )?;
1504
1505            let params = DistributeExcessIdleParams {
1506                present_value_params: PresentValueParams {
1507                    share_reserves: present_state.info.share_reserves,
1508                    bond_reserves: present_state.info.bond_reserves,
1509                    longs_outstanding: present_state.info.longs_outstanding,
1510                    share_adjustment: present_state.info.share_adjustment,
1511                    time_stretch: present_state.config.time_stretch,
1512                    vault_share_price: present_state.info.vault_share_price,
1513                    initial_vault_share_price: present_state.config.initial_vault_share_price,
1514                    minimum_share_reserves: present_state.config.minimum_share_reserves,
1515                    minimum_transaction_amount: present_state.config.minimum_transaction_amount,
1516                    long_average_time_remaining: long_average_time_remaining.into(),
1517                    short_average_time_remaining: short_average_time_remaining.into(),
1518                    shorts_outstanding: present_state.shorts_outstanding().into(),
1519                },
1520                starting_present_value: starting_present_value.into(),
1521                active_lp_total_supply: active_lp_total_supply.into(),
1522                withdrawal_shares_total_supply: withdrawal_shares_total_supply.into(),
1523                idle: present_state.calculate_idle_share_reserves().into(),
1524                net_curve_trade: net_curve_trade,
1525                original_share_reserves: present_state.share_reserves().into(),
1526                original_share_adjustment: present_state.share_adjustment(),
1527                original_bond_reserves: present_state.bond_reserves().into(),
1528            };
1529
1530            // Make the solidity call and compare to the Rust implementation.
1531            match mock
1532                .calculate_distribute_excess_idle_withdrawal_shares_redeemed(
1533                    params,
1534                    share_reserves_delta.into(),
1535                )
1536                .call()
1537                .await
1538            {
1539                Ok(expected) => {
1540                    let sol_withdrawal_shares_redeemed = expected;
1541                    let rust_withdrawal_shares_redeemed = U256::from(actual?);
1542                    assert_eq!(
1543                        sol_withdrawal_shares_redeemed,
1544                        rust_withdrawal_shares_redeemed,
1545                    );
1546                }
1547                Err(_) => {
1548                    assert!(actual.is_err())
1549                }
1550            }
1551        }
1552        Ok(())
1553    }
1554
1555    #[tokio::test]
1556    async fn fuzz_calculate_distribute_excess_idle_share_proceeds_net_long_edge_case() -> Result<()>
1557    {
1558        let chain = TestChain::new().await?;
1559        let alice = chain.alice().await?;
1560        let mock = chain.mock_lp_math();
1561
1562        // Fuzz the rust and solidity implementations against each other.
1563        let mut rng = thread_rng();
1564
1565        for _ in 0..*FAST_FUZZ_RUNS {
1566            // Generate random states.
1567            let original_state = rng.gen::<State>();
1568            let mut present_state = rng.gen::<State>();
1569
1570            // Generate random variables.
1571            let net_curve_trade = rng.gen_range(fixed_i256!(0)..=fixed!(1e24)).raw(); // 1 million
1572
1573            // active_lp_total_supply and _withdrawal_shares_total_supply are just the token supplies.
1574            // Neither are supplied in the PoolInfo so we need to make them here.  lp_total_supply is defined as:
1575            // lp_total_supply = active_lp_total_supply + withdrawal_shares_total_supply - withdrawal_shares_ready_to_withdraw
1576            // active_lp_total_supply = lp_total_supply - withdrawal_shares_total_supply + withdrawal_shares_ready_to_withdraw
1577            // We clip withdrawal_shares_total_supply to ensure active_lp_total_supply doesn't underflow.
1578            let active_lp_total_supply = present_state.lp_total_supply()
1579                + present_state.withdrawal_shares_ready_to_withdraw();
1580            let withdrawal_shares_total_supply = rng.gen_range(fixed!(0)..=active_lp_total_supply);
1581            let active_lp_total_supply = active_lp_total_supply - withdrawal_shares_total_supply;
1582
1583            // Make sure maturity times are in the future.
1584            let current_block_timestamp = alice.now().await?;
1585            present_state.info.long_average_maturity_time += current_block_timestamp;
1586            present_state.info.short_average_maturity_time += current_block_timestamp;
1587
1588            // This errors out a lot so we need to catch that here.
1589            let starting_present_value =
1590                match original_state.calculate_present_value(U256::from(current_block_timestamp)) {
1591                    Ok(result) => result,
1592                    Err(_) => continue,
1593                };
1594
1595            // Calculate idle capital.
1596            let idle = present_state.calculate_idle_share_reserves();
1597
1598            // Calculate the result from the Rust implementation.
1599            let actual = present_state
1600                .calculate_distribute_excess_idle_share_proceeds_net_long_edge_case_safe(
1601                    current_block_timestamp,
1602                    original_state.share_adjustment(),
1603                    original_state.share_reserves(),
1604                    starting_present_value,
1605                    active_lp_total_supply,
1606                    withdrawal_shares_total_supply,
1607                );
1608
1609            let long_average_time_remaining = present_state
1610                .calculate_scaled_normalized_time_remaining(
1611                    present_state.long_average_maturity_time(),
1612                    current_block_timestamp,
1613                );
1614            let short_average_time_remaining = present_state
1615                .calculate_scaled_normalized_time_remaining(
1616                    present_state.short_average_maturity_time(),
1617                    current_block_timestamp,
1618                );
1619            // Gather the parameters for the solidity call.  There are a lot
1620            // that aren't actually used, but the solidity call needs them.
1621            let params = DistributeExcessIdleParams {
1622                present_value_params: PresentValueParams {
1623                    share_reserves: present_state.info.share_reserves,
1624                    bond_reserves: present_state.info.bond_reserves,
1625                    longs_outstanding: present_state.info.longs_outstanding,
1626                    share_adjustment: present_state.info.share_adjustment,
1627                    time_stretch: present_state.config.time_stretch,
1628                    vault_share_price: present_state.info.vault_share_price,
1629                    initial_vault_share_price: present_state.config.initial_vault_share_price,
1630                    minimum_share_reserves: present_state.config.minimum_share_reserves,
1631                    minimum_transaction_amount: present_state.config.minimum_transaction_amount,
1632                    long_average_time_remaining: long_average_time_remaining.into(),
1633                    short_average_time_remaining: short_average_time_remaining.into(),
1634                    shorts_outstanding: present_state.shorts_outstanding().into(),
1635                },
1636                starting_present_value: starting_present_value.into(),
1637                active_lp_total_supply: active_lp_total_supply.into(),
1638                withdrawal_shares_total_supply: withdrawal_shares_total_supply.into(),
1639                idle: idle.into(),
1640                net_curve_trade: net_curve_trade,
1641                original_share_reserves: original_state.share_reserves().into(),
1642                original_share_adjustment: original_state.share_adjustment(),
1643                original_bond_reserves: original_state.bond_reserves().into(),
1644            };
1645
1646            // Make the solidity call and compare to the Rust implementation.
1647            match mock
1648                .calculate_distribute_excess_idle_share_proceeds_net_long_edge_case_safe(params)
1649                .call()
1650                .await
1651            {
1652                Ok(expected) => {
1653                    let (expected_result, expected_success) = expected;
1654                    let (actual_result, actual_success) = actual?;
1655                    assert_eq!(actual_result, FixedPoint::from(expected_result));
1656                    assert_eq!(actual_success, expected_success);
1657                }
1658                Err(_) => {
1659                    assert!(actual.is_err())
1660                }
1661            }
1662        }
1663        Ok(())
1664    }
1665
1666    #[tokio::test]
1667    async fn fuzz_should_short_circuit_distribute_excess_idle_share_proceeds() -> Result<()> {
1668        let chain = TestChain::new().await?;
1669        let mock = chain.mock_lp_math();
1670
1671        // Fuzz the rust and solidity implementations against each other.
1672        let mut rng = thread_rng();
1673
1674        for _ in 0..*FAST_FUZZ_RUNS {
1675            // Generate random states.
1676            let present_state = rng.gen::<State>();
1677
1678            let starting_present_value = rng.gen_range(fixed!(0)..=fixed!(1e24));
1679            let present_value = rng.gen_range(fixed!(0)..=fixed!(1e24));
1680            let active_lp_total_supply = rng.gen_range(fixed!(0)..=fixed!(1e24));
1681            let lp_total_supply = rng.gen_range(fixed!(0)..=fixed!(1e24));
1682
1683            // Calculate the result from the Rust implementation.
1684            let actual = present_state.should_short_circuit_distribute_excess_idle_share_proceeds(
1685                active_lp_total_supply,
1686                starting_present_value,
1687                lp_total_supply,
1688                present_value,
1689            );
1690
1691            // Gather the parameters for the solidity call.
1692            let mut params = DistributeExcessIdleParams::default();
1693            params.active_lp_total_supply = active_lp_total_supply.into();
1694            params.starting_present_value = starting_present_value.into();
1695
1696            // Make the solidity call and compare to the Rust implementation.
1697            match mock
1698                .should_short_circuit_distribute_excess_idle_share_proceeds(
1699                    params,
1700                    lp_total_supply.into(),
1701                    present_value.into(),
1702                )
1703                .call()
1704                .await
1705            {
1706                Ok(expected) => {
1707                    if expected == true {
1708                        assert!(actual? == true);
1709                    } else {
1710                        assert!(actual? == false);
1711                    }
1712                }
1713                Err(_) => {
1714                    assert!(actual.is_err())
1715                }
1716            }
1717        }
1718        Ok(())
1719    }
1720
1721    #[tokio::test]
1722    async fn fuzz_calculate_shares_delta_given_bonds_delta_derivative() -> Result<()> {
1723        let chain = TestChain::new().await?;
1724        let mock = chain.mock_lp_math();
1725
1726        // Fuzz the rust and solidity implementations against each other.
1727        let mut rng = thread_rng();
1728
1729        for _ in 0..*FAST_FUZZ_RUNS {
1730            // Generate random states.
1731            let original_state = rng.gen::<State>();
1732            let present_state = rng.gen::<State>();
1733
1734            // Get the bond amount, which in this case is equal to the net_curve_trade.
1735            let bond_amount = rng.gen_range(fixed_i256!(0)..=fixed!(1e24)).raw(); // 1 million
1736
1737            // Maturity time goes from 0 to position duration, so we'll just set
1738            // this to zero to make the math simpler.
1739            let current_block_timestamp = fixed!(0);
1740
1741            // Calcuulate the result from the Rust implementation.
1742            let actual = present_state.calculate_shares_delta_given_bonds_delta_derivative(
1743                bond_amount,
1744                original_state.share_reserves(),
1745                original_state.bond_reserves(),
1746                original_state.effective_share_reserves()?,
1747                original_state.share_adjustment(),
1748            );
1749
1750            // This errors out a lot so we need to catch that here.
1751            let starting_present_value_result =
1752                original_state.calculate_present_value(U256::from(current_block_timestamp));
1753            if starting_present_value_result.is_err() {
1754                continue;
1755            }
1756            let starting_present_value = starting_present_value_result?;
1757            let idle = present_state.calculate_idle_share_reserves();
1758
1759            // Gather the parameters for the solidity call.  There are a lot
1760            // that aren't actually used, but the solidity call needs them.
1761            let params = DistributeExcessIdleParams {
1762                present_value_params: PresentValueParams {
1763                    share_reserves: present_state.info.share_reserves,
1764                    bond_reserves: present_state.info.bond_reserves,
1765                    longs_outstanding: present_state.info.longs_outstanding,
1766                    share_adjustment: present_state.info.share_adjustment,
1767                    time_stretch: present_state.config.time_stretch,
1768                    vault_share_price: present_state.info.vault_share_price,
1769                    initial_vault_share_price: present_state.config.initial_vault_share_price,
1770                    minimum_share_reserves: present_state.config.minimum_share_reserves,
1771                    minimum_transaction_amount: present_state.config.minimum_transaction_amount,
1772                    long_average_time_remaining: present_state
1773                        .calculate_normalized_time_remaining(
1774                            present_state.long_average_maturity_time().into(),
1775                            current_block_timestamp.into(),
1776                        )
1777                        .into(),
1778                    short_average_time_remaining: present_state
1779                        .calculate_normalized_time_remaining(
1780                            present_state.short_average_maturity_time().into(),
1781                            current_block_timestamp.into(),
1782                        )
1783                        .into(),
1784                    shorts_outstanding: present_state.shorts_outstanding().into(),
1785                },
1786                starting_present_value: starting_present_value.into(),
1787                active_lp_total_supply: original_state.lp_total_supply().into(),
1788                withdrawal_shares_total_supply: uint256!(0),
1789                idle: idle.into(),
1790                net_curve_trade: bond_amount,
1791                original_share_reserves: original_state.share_reserves().into(),
1792                original_share_adjustment: original_state.share_adjustment(),
1793                original_bond_reserves: original_state.bond_reserves().into(),
1794            };
1795
1796            // Make the solidity call and compare to the Rust implementation.
1797            match mock
1798                .calculate_shares_delta_given_bonds_delta_derivative_safe(
1799                    params,
1800                    U256::from(original_state.effective_share_reserves()?),
1801                    bond_amount,
1802                )
1803                .call()
1804                .await
1805            {
1806                Ok(expected) => {
1807                    let (result, success) = expected;
1808                    if !success && result == uint256!(0) {
1809                        assert!(actual.is_err());
1810                    } else if success && result == uint256!(0) {
1811                        assert_eq!(actual?, fixed!(0));
1812                    } else {
1813                        assert_eq!(actual?, FixedPoint::from(expected.0));
1814                    }
1815                }
1816                Err(_) => {
1817                    assert!(actual.is_err())
1818                }
1819            }
1820        }
1821        Ok(())
1822    }
1823}