Skip to content

Commit

Permalink
calc abs max short guess (#202)
Browse files Browse the repository at this point in the history
improves the function for calculating the absolute max short.

We use a linear approximation that passes the test 99.67% of the time
(7441/7465 passed). There is an additional loop to ensure the remaining
tests pass. Future work should be to fix the linear estimate to remove
this loop -- it should not be required if the theory & code are correct.

Additional changes:
- removed the solidity comparison test for
`calculate_absolute_max_short`. This was failing in cases when solidity
was successful and erroneous. The new (rust) starting point obtained by
the approximate estimate can succeed when the old (solidity) function
fails, and it can also result in a lower final value than the solidity
version. Follow-up PRs will add more tests that concretely demonstrate
tolerances for final solvency after opening the max short. Once we are
happy with the Rust version, we can port the code back over to Solidity
& add back the parity tests.
- improved docstrings & comments for format & variable consistency
- renamed `calculate_spot_price` to `calculate_spot_price_down` and
added a `*_up` version -- this is helpful for over/under estimating the
price in the approximation code.
- made functions for calculating the minimum share reserves and the
total fees paid when opening a short to dry up duplicate versions.
- added a new check and cleaned up error messaging/comments in
`solvency_after_short`
- modified final liquidity provision in preamble to ensure short is
possible given exposure from earlier random trades.
- modified some tests ensure a short is possible before conducting the
test

## Derivation of approximate function


![image](https://github.com/user-attachments/assets/8e5664d7-b2d0-46ed-ae18-2e3736166d5d)

![image](https://github.com/user-attachments/assets/57a07c55-7e4d-4235-b094-603a41a8db99)

![image](https://github.com/user-attachments/assets/18b9b1cf-2f0f-443f-a154-fa84a9f891c6)

![image](https://github.com/user-attachments/assets/978cb4e1-91c2-4c1a-85a3-ac7f79080bce)

![image](https://github.com/user-attachments/assets/5a8afadf-037b-42fb-9d1a-ffdf6ac829b0)
  • Loading branch information
dpaiton authored Dec 18, 2024
1 parent a7021ad commit 7b44c25
Show file tree
Hide file tree
Showing 15 changed files with 435 additions and 229 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/rust_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:

- uses: actions-rs/toolchain@v1
with:
toolchain: nightly-2024-05-02
toolchain: nightly
override: true
components: rustfmt, clippy

Expand Down
2 changes: 1 addition & 1 deletion bindings/hyperdrivepy/src/hyperdrive_state_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl HyperdriveState {
}

pub fn calculate_spot_price(&self) -> PyResult<String> {
let result_fp = self.state.calculate_spot_price().map_err(|err| {
let result_fp = self.state.calculate_spot_price_down().map_err(|err| {
PyErr::new::<PyValueError, _>(format!("calculate_spot_price: {}", err))
})?;
let result = U256::from(result_fp).to_string();
Expand Down
19 changes: 12 additions & 7 deletions crates/hyperdrive-math/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,20 @@ impl State {
Self { config, info }
}

/// Calculates the pool's spot price.
pub fn calculate_spot_price(&self) -> Result<FixedPoint<U256>> {
YieldSpace::calculate_spot_price(self)
/// Calculates the pool's spot price, rounding down.
pub fn calculate_spot_price_down(&self) -> Result<FixedPoint<U256>> {
YieldSpace::calculate_spot_price_down(self)
}

/// Calculates the pool's spot price, rounding up.
pub fn calculate_spot_price_up(&self) -> Result<FixedPoint<U256>> {
YieldSpace::calculate_spot_price_up(self)
}

/// Calculate the pool's current spot (aka "fixed") rate.
pub fn calculate_spot_rate(&self) -> Result<FixedPoint<U256>> {
Ok(calculate_rate_given_fixed_price(
self.calculate_spot_price()?,
self.calculate_spot_price_down()?,
self.position_duration(),
))
}
Expand Down Expand Up @@ -364,8 +369,8 @@ mod tests {
state.info.shorts_outstanding = uint256!(0);
state.info.short_average_maturity_time = uint256!(0);
// Make sure we're still solvent
if state.calculate_spot_price()? < state.calculate_min_spot_price()?
|| state.calculate_spot_price()? > fixed!(1e18)
if state.calculate_spot_price_down()? < state.calculate_min_spot_price()?
|| state.calculate_spot_price_down()? > fixed!(1e18)
|| state.calculate_solvency().is_err()
{
continue;
Expand Down Expand Up @@ -403,7 +408,7 @@ mod tests {
new_state.info.share_reserves = target_share_reserves.into();
new_state.info.bond_reserves = target_bond_reserves.into();
if new_state.calculate_solvency().is_err()
|| new_state.calculate_spot_price()? > fixed!(1e18)
|| new_state.calculate_spot_price_down()? > fixed!(1e18)
{
continue;
}
Expand Down
4 changes: 2 additions & 2 deletions crates/hyperdrive-math/src/long/close.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ impl State {
) -> Result<FixedPoint<U256>> {
let bond_amount = bond_amount.into();

let spot_price = self.calculate_spot_price()?;
let spot_price = self.calculate_spot_price_down()?;
if spot_price > fixed!(1e18) {
return Err(eyre!("Negative fixed interest!"));
}
Expand Down Expand Up @@ -231,7 +231,7 @@ mod tests {
// Ensure curve_fee is smaller than spot_price to avoid overflows
// on the hyperdrive valuation, as that'd mean having to pay a larger
// amount of fees than the current value of the long.
let spot_price = state.calculate_spot_price()?;
let spot_price = state.calculate_spot_price_down()?;
if state.curve_fee() * (fixed!(1e18) - spot_price) > spot_price {
continue;
}
Expand Down
6 changes: 3 additions & 3 deletions crates/hyperdrive-math/src/long/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ impl State {
// NOTE: Round up to overestimate the curve fee.
Ok(self
.curve_fee()
.mul_up(fixed!(1e18).div_up(self.calculate_spot_price()?) - fixed!(1e18))
.mul_up(fixed!(1e18).div_up(self.calculate_spot_price_down()?) - fixed!(1e18))
.mul_up(base_amount))
}

Expand All @@ -43,7 +43,7 @@ impl State {
// NOTE: Round down to underestimate the governance curve fee.
Ok(curve_fee
.mul_down(self.governance_lp_fee())
.mul_down(self.calculate_spot_price()?))
.mul_down(self.calculate_spot_price_down()?))
}

/// Calculates the curve fee paid when closing longs for a given bond
Expand All @@ -69,7 +69,7 @@ impl State {
// NOTE: Round up to overestimate the curve fee.
Ok(self
.curve_fee()
.mul_up(fixed!(1e18) - self.calculate_spot_price()?)
.mul_up(fixed!(1e18) - self.calculate_spot_price_down()?)
.mul_up(bond_amount)
.mul_div_up(normalized_time_remaining, self.vault_share_price()))
}
Expand Down
13 changes: 7 additions & 6 deletions crates/hyperdrive-math/src/long/max.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ impl State {
/ (fixed!(1e18)
+ self
.curve_fee()
.mul_up(fixed!(1e18).div_up(self.calculate_spot_price()?) - fixed!(1e18)))
.mul_up(fixed!(1e18).div_up(self.calculate_spot_price_down()?) - fixed!(1e18)))
.mul_up(fixed!(1e18) - self.flat_fee()))
}

Expand Down Expand Up @@ -208,7 +208,8 @@ impl State {
+ self
.curve_fee()
.mul_up(
fixed!(1e18).div_up(self.calculate_spot_price()?) - fixed!(1e18),
fixed!(1e18).div_up(self.calculate_spot_price_down()?)
- fixed!(1e18),
)
.mul_up(fixed!(1e18) - self.flat_fee()))
.div_up(fixed!(1e18) - self.flat_fee()))
Expand All @@ -226,7 +227,7 @@ impl State {
//
// y_t = inner * ((1 + curveFee * (1 / p_0 - 1) * (1 - flatFee)) / (1 - flatFee)) ** (1 / t_s)
let fee_adjustment = self.curve_fee()
* (fixed!(1e18) / self.calculate_spot_price()? - fixed!(1e18))
* (fixed!(1e18) / self.calculate_spot_price_down()? - fixed!(1e18))
* (fixed!(1e18) - self.flat_fee());
let target_bond_reserves = ((fixed!(1e18) + fee_adjustment)
/ (fixed!(1e18) - self.flat_fee()))
Expand Down Expand Up @@ -265,7 +266,7 @@ impl State {
) -> Result<FixedPoint<U256>> {
// Calculate an initial estimate of the max long by using the spot price as
// our conservative price.
let spot_price = self.calculate_spot_price()?;
let spot_price = self.calculate_spot_price_down()?;
let guess = self.max_long_estimate(spot_price, spot_price, checkpoint_exposure)?;

// We know that the spot price is 1 when the absolute max base amount is
Expand Down Expand Up @@ -426,7 +427,7 @@ impl State {
base_amount: FixedPoint<U256>,
) -> Result<FixedPoint<U256>> {
let derivative = self.calculate_open_long_derivative(base_amount)?;
let spot_price = self.calculate_spot_price()?;
let spot_price = self.calculate_spot_price_down()?;
Ok(
(derivative
+ self.governance_lp_fee() * self.curve_fee() * (fixed!(1e18) - spot_price)
Expand Down Expand Up @@ -485,7 +486,7 @@ mod tests {
state.info.share_adjustment,
)?
.into(),
state.calculate_spot_price()?.into(),
state.calculate_spot_price_down()?.into(),
)
.call()
.await
Expand Down
6 changes: 3 additions & 3 deletions crates/hyperdrive-math/src/long/open.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ impl State {

// Finish computing the derivative.
derivative -=
self.curve_fee() * ((fixed!(1e18) / self.calculate_spot_price()?) - fixed!(1e18));
self.curve_fee() * ((fixed!(1e18) / self.calculate_spot_price_down()?) - fixed!(1e18));

Ok(derivative)
}
Expand Down Expand Up @@ -155,7 +155,7 @@ impl State {
) -> Result<FixedPoint<U256>> {
let state =
self.calculate_pool_state_after_open_long(base_amount, maybe_bond_pool_delta)?;
state.calculate_spot_price()
state.calculate_spot_price_down()
}

/// Calculate the spot rate after a long has been opened.
Expand Down Expand Up @@ -336,7 +336,7 @@ mod tests {
// Verify that the predicted spot price is equal to the ending spot
// price. These won't be exactly equal because the vault share price
// increases between the prediction and opening the long.
let actual_spot_price = bob.get_state().await?.calculate_spot_price()?;
let actual_spot_price = bob.get_state().await?.calculate_spot_price_down()?;
let delta = if actual_spot_price > expected_spot_price {
actual_spot_price - expected_spot_price
} else {
Expand Down
6 changes: 3 additions & 3 deletions crates/hyperdrive-math/src/long/targeted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ impl State {
// g'(x) = \phi_g \phi_c (1 - p_0)
let gov_fee_derivative = self.governance_lp_fee()
* self.curve_fee()
* (fixed!(1e18) - self.calculate_spot_price()?);
* (fixed!(1e18) - self.calculate_spot_price_down()?);

// a(x) = mu * (z_{e,0} + 1/c (x - g(x))
let inner_numerator = self.mu()
Expand Down Expand Up @@ -401,7 +401,7 @@ impl State {
}
let share_delta = ending_share_reserves - self.share_reserves();
let fees = fixed!(1e18)
- (fixed!(1e18) - self.calculate_spot_price()?)
- (fixed!(1e18) - self.calculate_spot_price_down()?)
* self.curve_fee()
* self.governance_lp_fee();
let base_delta = self.vault_share_price().mul_div_down(share_delta, fees);
Expand Down Expand Up @@ -620,7 +620,7 @@ mod tests {

// Check that our resulting price is under the max
let current_state = alice.get_state().await?;
let spot_price_after_long = current_state.calculate_spot_price()?;
let spot_price_after_long = current_state.calculate_spot_price_down()?;
assert!(
max_spot_price_before_long > spot_price_after_long,
"Resulting price is greater than the max."
Expand Down
10 changes: 5 additions & 5 deletions crates/hyperdrive-math/src/short/close.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ impl State {
Ok(fixed!(1e18)
- self
.curve_fee()
.mul_up(fixed!(1e18) - self.calculate_spot_price()?))
.mul_up(fixed!(1e18) - self.calculate_spot_price_down()?))
}

/// Calculates the amount of shares the trader will receive after fees for closing a short
Expand Down Expand Up @@ -224,7 +224,7 @@ impl State {
let mut state: State = self.clone();
state.info.bond_reserves -= bond_reserves_delta.into();
state.info.share_reserves += share_curve_delta.into();
state.calculate_spot_price()?
state.calculate_spot_price_down()?
};
let max_spot_price = self.calculate_close_short_max_spot_price()?;
if short_curve_spot_price > max_spot_price {
Expand All @@ -244,7 +244,7 @@ impl State {
let mut state: State = self.clone();
state.info.bond_reserves -= bond_reserves_delta.into();
state.info.share_reserves += share_curve_delta_with_fees.into();
state.calculate_spot_price()?
state.calculate_spot_price_down()?
};
if share_curve_delta_with_fees_spot_price > fixed!(1e18) {
return Err(eyre!("InsufficientLiquidity: Negative Interest"));
Expand Down Expand Up @@ -293,7 +293,7 @@ impl State {
let open_vault_share_price = open_vault_share_price.into();
let close_vault_share_price = close_vault_share_price.into();

let spot_price = self.calculate_spot_price()?;
let spot_price = self.calculate_spot_price_down()?;
if spot_price > fixed!(1e18) {
return Err(eyre!("Negative fixed interest!"));
}
Expand Down Expand Up @@ -513,7 +513,7 @@ mod tests {
let state = rng.gen::<State>();
let result = state.calculate_close_short(
(state.config.minimum_transaction_amount - 10).into(),
state.calculate_spot_price()?,
state.calculate_spot_price_down()?,
state.vault_share_price(),
0.into(),
0.into(),
Expand Down
19 changes: 17 additions & 2 deletions crates/hyperdrive-math/src/short/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl State {
// NOTE: Round up to overestimate the curve fee.
Ok(self
.curve_fee()
.mul_up(fixed!(1e18) - self.calculate_spot_price()?)
.mul_up(fixed!(1e18) - self.calculate_spot_price_down()?)
.mul_up(bond_amount))
}

Expand All @@ -43,6 +43,21 @@ impl State {
Ok(curve_fee.mul_down(self.governance_lp_fee()))
}

/// Calculate the total fees to be removed from the short principal when
/// opening a short for a given bond amount.
pub fn calculate_open_short_total_fee_shares(
&self,
bond_amount: FixedPoint<U256>,
) -> Result<FixedPoint<U256>> {
let curve_fee_base = self.open_short_curve_fee(bond_amount)?;
let curve_fee_shares = curve_fee_base.div_up(self.vault_share_price());
let gov_curve_fee_shares = self
.open_short_governance_fee(bond_amount, Some(curve_fee_base))?
.div_up(self.vault_share_price());
let total_fee_shares = curve_fee_shares - gov_curve_fee_shares;
Ok(total_fee_shares)
}

/// Calculates the curve fee paid when opening shorts with a given bond
/// amount.
///
Expand All @@ -65,7 +80,7 @@ impl State {
// NOTE: Round up to overestimate the curve fee.
Ok(self
.curve_fee()
.mul_up(fixed!(1e18) - self.calculate_spot_price()?)
.mul_up(fixed!(1e18) - self.calculate_spot_price_down()?)
.mul_up(bond_amount)
.mul_div_up(normalized_time_remaining, self.vault_share_price()))
}
Expand Down
Loading

0 comments on commit 7b44c25

Please sign in to comment.