Skip to content

Commit

Permalink
add calculate_short_proceeds_up (#123)
Browse files Browse the repository at this point in the history
# Resolved Issues
Partially addresses #29
and #21

# Description
These are steps towards implementing a parity implementation of open
short as well as targeted short.
- Add a function to calc short proceeds & round up, which is used in the
Open Short flow.
- Modifies calculate short proceeds down to look more like the up
version.

Since these functions are implemented on State, we preferentially use
State member variables (e.g. `vault_share_price`) instead of passing
arguments.

The short proceeds is given by:

$$
P_{\text{short}}(\Delta y) = \tfrac{\Delta y \cdot c_{1}}{c_{0} \cdot c}
+ \tfrac{\Delta y}{c} \cdot \phi_{f} - \Delta z(\Delta y)
$$
  • Loading branch information
dpaiton authored Jun 4, 2024
1 parent a6504e3 commit f7205da
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 26 deletions.
157 changes: 136 additions & 21 deletions crates/hyperdrive-math/src/short/close.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,28 +56,107 @@ impl State {
Ok(flat + curve)
}

// Calculates the proceeds in shares of closing a short position.
fn calculate_short_proceeds(
/// Calculates the proceeds in shares of closing a short position. This
/// takes into account the trading profits, the interest that was
/// earned by the short, the flat fee the short pays, and the amount of
/// margin that was released by closing the short. The math for the
/// short's proceeds in base is given by:
///
/// $$
/// proceeds = (\frac{c1}{c_0}
/// + \text{\phi_f}) \cdot \frac{\Delta y}{c}
/// - dz
/// $$
///
/// We convert the proceeds to shares by dividing by the current vault
/// share price. In the event that the interest is negative and
/// outweighs the trading profits and margin released, the short's
/// proceeds are marked to zero.
pub fn calculate_short_proceeds_up(
&self,
bond_amount: FixedPoint,
share_amount: FixedPoint,
open_vault_share_price: FixedPoint,
close_vault_share_price: FixedPoint,
vault_share_price: FixedPoint,
flat_fee: FixedPoint,
) -> FixedPoint {
let mut bond_factor = bond_amount
.mul_div_down(
close_vault_share_price,
// We round up here do avoid overestimating the share proceeds.
open_vault_share_price,
)
.div_down(vault_share_price);
bond_factor += bond_amount.mul_div_down(flat_fee, vault_share_price);

if bond_factor > share_amount {
// NOTE: Round up to overestimate the short proceeds.
//
// The total value is the amount of shares that underlies the bonds that
// were shorted. The bonds start by being backed 1:1 with base, and the
// total value takes into account all of the interest that has accrued
// since the short was opened.
//
// total_value = (c1 / (c0 * c)) * dy
let mut total_value = bond_amount
.mul_div_up(close_vault_share_price, open_vault_share_price)
.div_up(self.vault_share_price());

// NOTE: Round up to overestimate the short proceeds.
//
// We increase the total value by the flat fee amount, because it is
// included in the total amount of capital underlying the short.
total_value += bond_amount.mul_div_up(self.flat_fee(), self.vault_share_price());

// If the interest is more negative than the trading profits and margin
// released, then the short proceeds are marked to zero. Otherwise, we
// calculate the proceeds as the sum of the trading proceeds, the
// interest proceeds, and the margin released.
if total_value > share_amount {
// proceeds = (c1 / c0 * c) * dy - dz
bond_factor - share_amount
total_value - share_amount
} else {
fixed!(0)
}
}

/// Calculates the proceeds in shares of closing a short position. This
/// takes into account the trading profits, the interest that was
/// earned by the short, the flat fee the short pays, and the amount of
/// margin that was released by closing the short. The math for the
/// short's proceeds in base is given by:
///
/// $$
/// proceeds = (\frac{c1}{c_0 \cdot c}
/// + \text{\phi_f}) \cdot \frac{\Delta y}{c}
/// - dz
/// $$
///
/// We convert the proceeds to shares by dividing by the current vault
/// share price. In the event that the interest is negative and
/// outweighs the trading profits and margin released, the short's
/// proceeds are marked to zero.
fn calculate_short_proceeds_down(
&self,
bond_amount: FixedPoint,
share_amount: FixedPoint,
open_vault_share_price: FixedPoint,
close_vault_share_price: FixedPoint,
) -> FixedPoint {
// NOTE: Round down to underestimate the short proceeds.
//
// The total value is the amount of shares that underlies the bonds that
// were shorted. The bonds start by being backed 1:1 with base, and the
// total value takes into account all of the interest that has accrued
// since the short was opened.
//
// total_value = (c1 / (c0 * c)) * dy
let mut total_value = bond_amount
.mul_div_down(close_vault_share_price, open_vault_share_price)
.div_down(self.vault_share_price());

// NOTE: Round down to underestimate the short proceeds.
//
// We increase the total value by the flat fee amount, because it is
// included in the total amount of capital underlying the short.
total_value += bond_amount.mul_div_down(self.flat_fee(), self.vault_share_price());

// If the interest is more negative than the trading profits and margin
// released, then the short proceeds are marked to zero. Otherwise, we
// calculate the proceeds as the sum of the trading proceeds, the
// interest proceeds, and the margin released.
if total_value > share_amount {
// proceeds = (c1 / c0 * c) * dy - dz
total_value - share_amount
} else {
fixed!(0)
}
Expand Down Expand Up @@ -165,13 +244,11 @@ impl State {
+ self.close_short_flat_fee(bond_amount, maturity_time, current_time);

// Calculate the share proceeds owed to the short.
Ok(self.calculate_short_proceeds(
Ok(self.calculate_short_proceeds_down(
bond_amount,
share_reserves_delta_with_fees,
open_vault_share_price,
close_vault_share_price,
self.vault_share_price(),
self.flat_fee(),
))
}
}
Expand All @@ -186,7 +263,7 @@ mod tests {
use super::*;

#[tokio::test]
async fn fuzz_calculate_short_proceeds() -> Result<()> {
async fn fuzz_calculate_short_proceeds_up() -> Result<()> {
let chain = TestChain::new().await?;

// Fuzz the rust and solidity implementations against each other.
Expand All @@ -197,13 +274,51 @@ mod tests {
let share_amount = rng.gen_range(fixed!(0)..=bond_amount);
let open_vault_share_price = rng.gen_range(fixed!(0)..=state.vault_share_price());
let actual = panic::catch_unwind(|| {
state.calculate_short_proceeds(
state.calculate_short_proceeds_up(
bond_amount,
share_amount,
open_vault_share_price,
state.vault_share_price(),
)
});
match chain
.mock_hyperdrive_math()
.calculate_short_proceeds_up(
bond_amount.into(),
share_amount.into(),
open_vault_share_price.into(),
state.vault_share_price().into(),
state.vault_share_price().into(),
state.flat_fee().into(),
)
.call()
.await
{
Ok(expected) => assert_eq!(actual.unwrap(), FixedPoint::from(expected)),
Err(_) => assert!(actual.is_err()),
}
}

Ok(())
}

#[tokio::test]
async fn fuzz_calculate_short_proceeds_down() -> Result<()> {
let chain = TestChain::new().await?;

// Fuzz the rust and solidity implementations against each other.
let mut rng = thread_rng();
for _ in 0..*FAST_FUZZ_RUNS {
let state = rng.gen::<State>();
let bond_amount = rng.gen_range(fixed!(0)..=state.bond_reserves());
let share_amount = rng.gen_range(fixed!(0)..=bond_amount);
let open_vault_share_price = rng.gen_range(fixed!(0)..=state.vault_share_price());
let actual = panic::catch_unwind(|| {
state.calculate_short_proceeds_down(
bond_amount,
share_amount,
open_vault_share_price,
state.vault_share_price(),
state.flat_fee(),
)
});
match chain
Expand Down
7 changes: 2 additions & 5 deletions crates/hyperdrive-math/src/short/open.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,12 +293,9 @@ mod tests {
use hyperdrive_test_utils::{
agent::Agent,
chain::{ChainClient, TestChain},
constants::{BOB, FAST_FUZZ_RUNS, FUZZ_RUNS},
};
use hyperdrive_wrappers::wrappers::{
ihyperdrive::{Checkpoint, Options},
mock_erc4626::MockERC4626,
constants::{FAST_FUZZ_RUNS, FUZZ_RUNS},
};
use hyperdrive_wrappers::wrappers::ihyperdrive::{Checkpoint, Options};
use rand::{thread_rng, Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;

Expand Down

0 comments on commit f7205da

Please sign in to comment.