Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add calculate_short_proceeds_up #123

Merged
merged 3 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading