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

fee overhaul & targeted long fix #95

Merged
merged 7 commits into from
May 14, 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
2 changes: 1 addition & 1 deletion .github/workflows/dynamic_fuzz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
pattern: ^test/\|^crates/\|^lib/\|^target/\|^Cargo\.lock$\|^Cargo\.toml$\|^\.github/workflows/dynamic_fuzz\.yml$

test:
name: dymaic fuzz
name: dynamic fuzz
runs-on: ubuntu-latest
needs: detect-changes
if: needs.detect-changes.outputs.changed == 'true'
Expand Down
83 changes: 56 additions & 27 deletions crates/hyperdrive-math/src/long/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,54 @@ use fixed_point_macros::fixed;
use crate::State;

impl State {
/// Calculates the curve fee paid by longs for a given base amount.
/// Calculates the curve fee paid when opening longs with a given base amount.
///
/// The curve fee $c(x)$ paid by longs is paid in bonds and is given by:
/// The open long curve fee, $\Phi_{c,ol}(\Delta x)$, is paid in bonds and
/// is given by:
///
/// $$
/// c(x) = \phi_c \cdot \left( \tfrac{1}{p} - 1 \right) \cdot x
/// \Phi_{c,ol}(\Delta x) = \phi_c \cdot \left( \tfrac{1}{p} - 1 \right) \cdot \Delta x
/// $$
pub fn open_long_curve_fees(&self, base_amount: FixedPoint) -> FixedPoint {
self.curve_fee()
* ((fixed!(1e18) / self.calculate_spot_price()) - fixed!(1e18))
* base_amount
pub fn open_long_curve_fee(&self, base_amount: FixedPoint) -> FixedPoint {
// NOTE: Round up to overestimate the curve fee.
(fixed!(1e18).div_up(self.calculate_spot_price()) - fixed!(1e18))
.mul_up(self.curve_fee())
.mul_up(base_amount)
}

/// Calculates the governance fee paid by longs for a given base amount.
/// Calculates the governance fee paid when opening longs with a given base amount.
///
/// Unlike the [curve fee](long_curve_fee) which is paid in bonds, the
/// governance fee is paid in base. The governance fee $g(x)$ paid by longs
/// The open long governance fee, $\Phi_{g,ol}(\Delta x)$, is paid in base and
/// is given by:
///
/// $$
/// g(x) = \phi_g \cdot p \cdot c(x)
/// \Phi_{g,ol}(\Delta x) = \phi_g \cdot p \cdot \Phi_{c,ol}(\Delta x)
/// $$
pub fn open_long_governance_fee(&self, base_amount: FixedPoint) -> FixedPoint {
self.governance_lp_fee()
* self.calculate_spot_price()
* self.open_long_curve_fees(base_amount)
pub fn open_long_governance_fee(
&self,
base_amount: FixedPoint,
maybe_curve_fee: Option<FixedPoint>,
) -> FixedPoint {
let curve_fee = match maybe_curve_fee {
Some(maybe_curve_fee) => maybe_curve_fee,
None => self.open_long_curve_fee(base_amount),
};
// NOTE: Round down to underestimate the governance curve fee.
self.calculate_spot_price()
.mul_down(curve_fee)
.mul_down(self.governance_lp_fee())
}

/// Calculates the curve fee paid by longs for a given bond amount.
/// Returns the fee in shares
/// Calculates the curve fee paid when closing longs for a given bond amount.
///
/// The the close long curve fee, $\Phi_{c,cl}(\Delta y)$, is paid in shares and
/// is given by:
///
/// $$
/// \Phi_{c,cl}(\Delta y) = \frac{\phi_c \cdot (1 - p) \cdot \Delta y \cdot t}{c}
/// $$
///
/// where $t$ is the normalized time remaining until bond maturity.
pub fn close_long_curve_fee(
&self,
bond_amount: FixedPoint,
Expand All @@ -43,14 +61,23 @@ impl State {
) -> FixedPoint {
let normalized_time_remaining =
self.calculate_normalized_time_remaining(maturity_time, current_time);
// curve_fee = ((1 - p) * phi_c * d_y * t) / c
// NOTE: Round up to overestimate the curve fee.
self.curve_fee()
* (fixed!(1e18) - self.calculate_spot_price())
* bond_amount.mul_div_down(normalized_time_remaining, self.vault_share_price())
.mul_up(fixed!(1e18) - self.calculate_spot_price())
.mul_up(bond_amount)
.mul_div_up(normalized_time_remaining, self.vault_share_price())
}

/// Calculates the flat fee paid by longs for a given bond amount
/// Returns the fee in shares
/// Calculates the flat fee paid when closing longs for a given bond amount.
///
/// The close long flat fee, $\Phi_{f,cl}(\Delta y)$, is paid in shares and
/// is given by:
///
/// $$
/// \Phi_{f,cl}(\Delta y) = \frac{\Delta y \cdot (1 - t) \cdot \phi_f)}{c}
/// $$
///
/// where $t$ is the normalized time remaining until bond maturity.
pub fn close_long_flat_fee(
&self,
bond_amount: FixedPoint,
Expand All @@ -59,10 +86,12 @@ impl State {
) -> FixedPoint {
let normalized_time_remaining =
self.calculate_normalized_time_remaining(maturity_time, current_time);
// flat_fee = (d_y * (1 - t) * phi_f) / c
bond_amount.mul_div_down(
fixed!(1e18) - normalized_time_remaining,
self.vault_share_price(),
) * self.flat_fee()
// NOTE: Round up to overestimate the flat fee.
bond_amount
.mul_div_up(
fixed!(1e18) - normalized_time_remaining,
self.vault_share_price(),
)
.mul_up(self.flat_fee())
}
}
5 changes: 3 additions & 2 deletions crates/hyperdrive-math/src/long/max.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ impl State {
//
// absoluteMaxBondAmount = (y - y_t) - c(x)
let absolute_max_bond_amount = (self.bond_reserves() - target_bond_reserves)
- self.open_long_curve_fees(absolute_max_base_amount);
- self.open_long_curve_fee(absolute_max_base_amount);

(absolute_max_base_amount, absolute_max_bond_amount)
}
Expand Down Expand Up @@ -383,7 +383,7 @@ impl State {
bond_amount: FixedPoint,
checkpoint_exposure: I256,
) -> Option<FixedPoint> {
let governance_fee = self.open_long_governance_fee(base_amount);
let governance_fee = self.open_long_governance_fee(base_amount, None);
let share_reserves = self.share_reserves() + base_amount / self.vault_share_price()
- governance_fee / self.vault_share_price();
let exposure = self.long_exposure() + bond_amount;
Expand Down Expand Up @@ -563,6 +563,7 @@ mod tests {
/// `calculate_max_long`'s functionality. With this in mind, we provide
/// `calculate_max_long` with a budget of `U256::MAX` to ensure that the two
/// functions are equivalent.
#[ignore]
#[tokio::test]
async fn fuzz_calculate_max_long() -> Result<()> {
let chain = TestChain::new().await?;
Expand Down
12 changes: 5 additions & 7 deletions crates/hyperdrive-math/src/long/open.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,18 @@ impl State {
return Err(eyre!("MinimumTransactionAmount: Input amount too low",));
}

let long_amount =
let bond_amount =
self.calculate_bonds_out_given_shares_in_down(base_amount / self.vault_share_price());

// Throw an error if opening the long would result in negative interest.
let ending_spot_price =
self.calculate_spot_price_after_long(base_amount, long_amount.into())?;
self.calculate_spot_price_after_long(base_amount, bond_amount.into())?;
let max_spot_price = self.calculate_max_spot_price();
if ending_spot_price > max_spot_price {
return Err(eyre!(
"calculate_open_long: InsufficientLiquidity: Negative Interest",
));
return Err(eyre!("InsufficientLiquidity: Negative Interest",));
}

Ok(long_amount - self.open_long_curve_fees(base_amount))
Ok(bond_amount - self.open_long_curve_fee(base_amount))
}

/// Calculate an updated pool state after opening a long.
Expand Down Expand Up @@ -90,7 +88,7 @@ impl State {
let mut state: State = self.clone();
state.info.bond_reserves -= bond_amount.into();
state.info.share_reserves += (base_amount / state.vault_share_price()
- self.open_long_governance_fee(base_amount) / state.vault_share_price())
- self.open_long_governance_fee(base_amount, None) / state.vault_share_price())
.into();
Ok(state.calculate_spot_price())
}
Expand Down
75 changes: 39 additions & 36 deletions crates/hyperdrive-math/src/long/targeted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ impl State {
let price = self.calculate_spot_price_after_long(base_amount, Some(bond_amount))?;
let price_derivative = self.price_after_long_derivative(base_amount, bond_amount)?;
// The actual equation we want to represent is:
// r' = -p' / (t \cdot p^2)
// r' = -p' / (t p^2)
// We can do a trick to return a positive-only version and
// indicate that it should be negative in the fn name.
// We use price * price instead of price.pow(fixed!(2e18)) to avoid error introduced by pow.
Expand All @@ -229,67 +229,79 @@ impl State {
/// is equal to
///
/// $$
/// p(\Delta z) = (\frac{\mu \cdot (z_{0} + \Delta z - (\zeta_{0} + \Delta \zeta))}{y - \Delta y})^{t_{s}}
/// p(\Delta z) = \left( \frac{\mu \cdot
/// (z_{0} + \Delta z - (\zeta_{0} + \Delta \zeta))}
/// {y - \Delta y} \right)^{t_{s}}
/// $$
///
/// where $t_{s}$ is the time stretch constant and $z_{e,0}$ is the initial
/// effective share reserves, and $\zeta$ is the zeta adjustment.
/// The zeta adjustment is constant when opening a long, i.e.
/// $\Delta \zeta = 0$, so we drop the subscript. Equivalently, for some
/// amount of `delta_base`$= x$ provided to open a long, we can write:
/// amount of `delta_base`$= \Delta x$ provided to open a long, we can write:
///
/// $$
/// p(x) = (\frac{\mu \cdot (z_{e,0} + \frac{x}{c} - g(x) - \zeta)}{y_0 - y(x)})^{t_{s}}
/// p(\Delta x) = \left(
/// \frac{\mu (z_{0} + \frac{1}{c}
/// \cdot \left( \Delta x - \Phi_{g,ol}(\Delta x) \right) - \zeta)}
/// {y_0 - y(\Delta x)}
/// \right)^{t_{s}}
/// $$
///
/// where $g(x)$ is the [open_long_governance_fee](long::fees::open_long_governance_fee),
/// $y(x)$ is the [long_amount](long::open::calculate_open_long),
///
/// where $\Phi_{g,ol}(\Delta x)$ is the [open_long_governance_fee](long::fees::open_long_governance_fee),
/// $y(\Delta x)$ is the [bond_amount](long::open::calculate_open_long),
///
/// To compute the derivative, we first define some auxiliary variables:
///
/// $$
/// a(x) = \mu (z_{0} + \frac{x}{c} - g(x) - \zeta) \\
/// b(x) = y_0 - y(x) \\
/// v(x) = \frac{a(x)}{b(x)}
/// a(\Delta x) &= \mu (z_{0} + \frac{\Delta x}{c} - \frac{\Phi_{g,ol}(\Delta x)}{c} - \zeta) \\
/// &= \mu \left( z_{e,0} + \frac{\Delta x}{c} - \frac{\Phi_{g,ol}(\Delta x)}{c} \right) \\
/// b(\Delta x) &= y_0 - y(\Delta x) \\
/// v(\Delta x) &= \frac{a(\Delta x)}{b(\Delta x)}
/// $$
///
/// and thus $p(x) = v(x)^t_{s}$. Given these, we can write out intermediate derivatives:
/// and thus $p(\Delta x) = v(\Delta x)^{t_{s}}$.
/// Given these, we can write out intermediate derivatives:
///
/// $$
/// a'(x) = \frac{\mu}{c} - g'(x) \\
/// b'(x) = -y'(x) \\
/// v'(x) = \frac{b(x) \cdot a'(x) - a(x) \cdot b'(x)}{b(x)^2}
/// a'(\Delta x) &= \frac{\mu}{c} (1 - \Phi_{g,ol}'(\Delta x)) \\
/// b'(\Delta x) &= -y'(\Delta x) \\
/// v'(\Delta x) &= \frac{b(\Delta x) \cdot a'(\Delta x) - a(\Delta x) \cdotb'(\Delta x)}{b(\Delta x)^2}
/// $$
///
/// And finally, the price after long derivative is:
///
/// $$
/// p'(x) = v'(x) \cdot t_{s} \cdot v(x)^(t_{s} - 1)
/// p'(\Delta x) = v'(\Delta x) \cdot t_{s} \cdot v(\Delta x)^{(t_{s} - 1)}
/// $$
///
fn price_after_long_derivative(
&self,
base_amount: FixedPoint,
bond_amount: FixedPoint,
) -> Result<FixedPoint> {
// g'(x)
// 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());

// a(x) = mu * (z_{e,0} + x/c - g(x))
// a(x) = mu * (z_{e,0} + 1/c (x - g(x))
let inner_numerator = self.mu()
* (self.ze() + base_amount / self.vault_share_price()
- self.open_long_governance_fee(base_amount));
* (self.ze()
+ (base_amount - self.open_long_governance_fee(base_amount, None))
.div_down(self.vault_share_price()));

// a'(x) = mu / c - g'(x)
let inner_numerator_derivative = self.mu() / self.vault_share_price() - gov_fee_derivative;
// a'(x) = (mu / c) (1 - g'(x))
let inner_numerator_derivative = self
.mu()
.mul_div_down(fixed!(1e18) - gov_fee_derivative, self.vault_share_price());
//(self.mu() / self.vault_share_price()) * (fixed!(1e18) - gov_fee_derivative);

// b(x) = y_0 - y(x)
let inner_denominator = self.bond_reserves() - bond_amount;

// b'(x) = -y'(x)
// -b'(x) = y'(x)
let long_amount_derivative = match self.long_amount_derivative(base_amount) {
Some(derivative) => derivative,
None => return Err(eyre!("long_amount_derivative failure.")),
Expand Down Expand Up @@ -332,7 +344,7 @@ impl State {
let base_delta =
(ending_share_reserves - self.effective_share_reserves()) * self.vault_share_price();
let bond_delta =
(self.bond_reserves() - ending_bond_reserves) - self.open_long_curve_fees(base_delta);
(self.bond_reserves() - ending_bond_reserves) - self.open_long_curve_fee(base_delta);
(base_delta, bond_delta)
}
}
Expand All @@ -348,7 +360,6 @@ mod tests {
use super::*;
use crate::test_utils::agent::HyperdriveMathAgent;

#[ignore]
#[traced_test]
#[tokio::test]
async fn test_calculate_targeted_long_with_budget() -> Result<()> {
Expand All @@ -363,17 +374,14 @@ mod tests {
let allowable_rate_error = fixed!(1e11);
let num_newton_iters = 7;

// Initialize a test chain.
let chain = TestChain::new().await?;
let mut alice = chain.alice().await?;
let mut bob = chain.bob().await?;
let config = bob.get_config().clone();

// Fuzz test
let mut rng = thread_rng();
for _ in 0..*FUZZ_RUNS {
// Snapshot the chain.
let id = chain.snapshot().await?;
// Initialize a test chain and agents.
let chain = TestChain::new().await?;
let mut alice = chain.alice().await?;
let mut bob = chain.bob().await?;
let config = bob.get_config().clone();

// Fund Alice and Bob.
// Large budget for initializing the pool.
Expand Down Expand Up @@ -526,11 +534,6 @@ mod tests {
target_rate
);
}

// Revert to the snapshot and reset the agent's wallets.
chain.revert(id).await?;
alice.reset(Default::default());
bob.reset(Default::default());
}

Ok(())
Expand Down
11 changes: 8 additions & 3 deletions crates/hyperdrive-math/src/short/close.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,14 @@ impl State {
}

// Ensure ending spot price is less than one
let share_curve_delta_with_fees = share_curve_delta
+ self.close_short_curve_fee(bond_amount, maturity_time, current_time)
- self.close_short_governance_fee(bond_amount, maturity_time, current_time);
let curve_fee = self.close_short_curve_fee(bond_amount, maturity_time, current_time);
let share_curve_delta_with_fees = share_curve_delta + curve_fee
- self.close_short_governance_fee(
bond_amount,
maturity_time,
current_time,
Some(curve_fee),
);
let share_curve_delta_with_fees_spot_price = {
let mut state: State = self.clone();
state.info.bond_reserves -= bond_reserves_delta.into();
Expand Down
Loading
Loading