From cdef68da4c46b191269b4cca799d4702005a70fd Mon Sep 17 00:00:00 2001 From: Dylan Date: Thu, 11 Apr 2024 21:04:07 -0500 Subject: [PATCH 1/7] removes redundant import --- crates/fixed-point/src/lib.rs | 3 +-- crates/hyperdrive-math/src/yield_space.rs | 1 - crates/test-utils/src/bin/migrate.rs | 5 +---- crates/test-utils/src/bin/testnet_deployment.rs | 4 +--- 4 files changed, 3 insertions(+), 10 deletions(-) diff --git a/crates/fixed-point/src/lib.rs b/crates/fixed-point/src/lib.rs index 9b6d2f612..11242f685 100644 --- a/crates/fixed-point/src/lib.rs +++ b/crates/fixed-point/src/lib.rs @@ -480,8 +480,7 @@ impl UniformSampler for UniformFixedPoint { mod tests { use std::panic; - use eyre::Result; - use rand::{thread_rng, Rng}; + use rand::thread_rng; use test_utils::{chain::TestChain, constants::FAST_FUZZ_RUNS}; use super::*; diff --git a/crates/hyperdrive-math/src/yield_space.rs b/crates/hyperdrive-math/src/yield_space.rs index 7a3f2bd6e..ea34045eb 100644 --- a/crates/hyperdrive-math/src/yield_space.rs +++ b/crates/hyperdrive-math/src/yield_space.rs @@ -344,7 +344,6 @@ pub trait YieldSpace { mod tests { use std::panic; - use eyre::Result; use rand::{thread_rng, Rng}; use test_utils::{chain::TestChain, constants::FAST_FUZZ_RUNS}; diff --git a/crates/test-utils/src/bin/migrate.rs b/crates/test-utils/src/bin/migrate.rs index 7ea6c9528..5ab712333 100644 --- a/crates/test-utils/src/bin/migrate.rs +++ b/crates/test-utils/src/bin/migrate.rs @@ -5,10 +5,7 @@ use std::{ use ethers::signers::LocalWallet; use eyre::Result; -use test_utils::{ - chain::{Chain, TestChainConfig}, - constants::ALICE, -}; +use test_utils::chain::{Chain, TestChainConfig}; #[tokio::main] async fn main() -> Result<()> { diff --git a/crates/test-utils/src/bin/testnet_deployment.rs b/crates/test-utils/src/bin/testnet_deployment.rs index ce55456e6..951001eff 100644 --- a/crates/test-utils/src/bin/testnet_deployment.rs +++ b/crates/test-utils/src/bin/testnet_deployment.rs @@ -21,13 +21,11 @@ /// /// After deploying these contracts and setting up the deployer coordinators, /// this script will transfer ownership of the factory to a specified address. -use std::fs::{create_dir_all, File}; use std::{env, sync::Arc}; use ethers::{ core::utils::keccak256, - middleware::Middleware, - signers::{LocalWallet, Signer}, + signers::LocalWallet, types::{Address, U256}, }; use eyre::Result; From 1560f32f59c9c04e594950e1e3a2ab1ecdae4ce1 Mon Sep 17 00:00:00 2001 From: Dylan Date: Thu, 11 Apr 2024 21:14:48 -0500 Subject: [PATCH 2/7] moves long functions Functions that need to be shared by targeted_short are moved to a more accessile location. Functions that hae duplicate purpose are consolidated. --- crates/hyperdrive-math/src/lib.rs | 53 ++++++++++ crates/hyperdrive-math/src/long/open.rs | 11 ++ crates/hyperdrive-math/src/long/targeted.rs | 108 ++++---------------- 3 files changed, 84 insertions(+), 88 deletions(-) diff --git a/crates/hyperdrive-math/src/lib.rs b/crates/hyperdrive-math/src/lib.rs index 973d1d42a..f9735fc25 100644 --- a/crates/hyperdrive-math/src/lib.rs +++ b/crates/hyperdrive-math/src/lib.rs @@ -146,6 +146,59 @@ impl State { } } + /// Calculates the pool reserve levels to achieve a target interest rate. + /// This calculation does not take Hyperdrive's solvency constraints or exposure + /// into account and shouldn't be used directly. + /// + /// The price for a given fixed-rate is given by $p = 1 / (r \cdot t + 1)$, where + /// $r$ is the fixed-rate and $t$ is the annualized position duration. The + /// price for a given pool reserves is given by $p = \frac{\mu z}{y}^t_{s}$, + /// where $\mu$ is the initial share price and $t_{s}$ is the time stretch + /// constant. By setting these equal we can solve for the pool reserve levels + /// as a function of a target rate. + /// + /// For some target rate, $r_t$, the pool share reserves, $z_t$, must be: + /// + /// $$ + /// z_t = \frac{1}{\mu} \left( + /// \frac{k}{\frac{c}{\mu} + \left( + /// (r_t \cdot t + 1)^{\frac{1}{t_{s}}} + /// \right)^{1 - t_{s}}} + /// \right)^{\tfrac{1}{1 - t_{s}}} + /// $$ + /// + /// and the pool bond reserves, $y_t$, must be: + /// + /// $$ + /// y_t = \left( + /// \frac{k}{ \frac{c}{\mu} + \left( + /// \left( r_t \cdot t + 1 \right)^{\frac{1}{t_{s}}} + /// \right)^{1 - t_{s}}} + /// \right)^{1 - t_{s}} \left( r_t t + 1 \right)^{\frac{1}{t_{s}}} + /// $$ + fn reserves_given_rate_ignoring_exposure>( + &self, + target_rate: F, + ) -> (FixedPoint, FixedPoint) { + let target_rate = target_rate.into(); + + // First get the target share reserves + let c_over_mu = self + .vault_share_price() + .div_up(self.initial_vault_share_price()); + let scaled_rate = (target_rate.mul_up(self.annualized_position_duration()) + fixed!(1e18)) + .pow(fixed!(1e18) / self.time_stretch()); + let inner = (self.k_down() + / (c_over_mu + scaled_rate.pow(fixed!(1e18) - self.time_stretch()))) + .pow(fixed!(1e18) / (fixed!(1e18) - self.time_stretch())); + let target_share_reserves = inner / self.initial_vault_share_price(); + + // Then get the target bond reserves. + let target_bond_reserves = inner * scaled_rate; + + (target_share_reserves, target_bond_reserves) + } + /// Config /// fn position_duration(&self) -> FixedPoint { diff --git a/crates/hyperdrive-math/src/long/open.rs b/crates/hyperdrive-math/src/long/open.rs index ca5f34de8..60ddfb5d2 100644 --- a/crates/hyperdrive-math/src/long/open.rs +++ b/crates/hyperdrive-math/src/long/open.rs @@ -67,6 +67,17 @@ impl State { /// Calculate the spot rate after a long has been opened. /// If a bond_amount is not provided, then one is estimated using `calculate_open_long`. + /// + /// We calculate the rate for a fixed length of time as: + /// $$ + /// r(x) = (1 - p(x)) / (p(x) t) + /// $$ + /// + /// where $p(x)$ is the spot price after a long for `delta_base`$= x$ and + /// t is the normalized position druation. + /// + /// In this case, we use the resulting spot price after a hypothetical long + /// for `base_amount` is opened. pub fn calculate_spot_rate_after_long( &self, base_amount: FixedPoint, diff --git a/crates/hyperdrive-math/src/long/targeted.rs b/crates/hyperdrive-math/src/long/targeted.rs index eda833054..1bad8c3c0 100644 --- a/crates/hyperdrive-math/src/long/targeted.rs +++ b/crates/hyperdrive-math/src/long/targeted.rs @@ -64,10 +64,11 @@ impl State { let (target_share_reserves, target_bond_reserves) = self.reserves_given_rate_ignoring_exposure(target_rate); let (mut target_base_delta, target_bond_delta) = - self.trade_deltas_from_reserves(target_share_reserves, target_bond_reserves); + self.long_trade_deltas_from_reserves(target_share_reserves, target_bond_reserves); // Determine what rate was achieved. - let resulting_rate = self.rate_after_long(target_base_delta, Some(target_bond_delta))?; + let resulting_rate = + self.calculate_spot_rate_after_long(target_base_delta, Some(target_bond_delta))?; // The estimated long will usually underestimate because the realized price // should always be greater than the spot price. @@ -112,8 +113,10 @@ impl State { let possible_target_bond_delta = self .calculate_open_long(possible_target_base_delta) .unwrap(); - let resulting_rate = - self.rate_after_long(possible_target_base_delta, Some(possible_target_bond_delta))?; + let resulting_rate = self.calculate_spot_rate_after_long( + possible_target_base_delta, + Some(possible_target_bond_delta), + )?; // We assume that the loss is positive only because Newton's // method will always underestimate. @@ -122,6 +125,8 @@ impl State { "We overshot the zero-crossing during Newton's method.", )); } + // The loss is $l(x) = r(x) - r_t$ for some rate after a long + // is opened, $r(x)$, and target rate, $r_t$. let loss = resulting_rate - target_rate; // If we've done it (solvent & within error), then return the value. @@ -171,8 +176,10 @@ impl State { let possible_target_bond_delta = self .calculate_open_long(possible_target_base_delta) .unwrap(); - let resulting_rate = - self.rate_after_long(possible_target_base_delta, Some(possible_target_bond_delta))?; + let resulting_rate = self.calculate_spot_rate_after_long( + possible_target_base_delta, + Some(possible_target_bond_delta), + )?; if target_rate > resulting_rate { return Err(eyre!( "We overshot the zero-crossing after Newton's method.", @@ -189,29 +196,6 @@ impl State { Ok(possible_target_base_delta) } - /// The fixed rate after a long has been opened. - /// - /// We calculate the rate for a fixed length of time as: - /// $$ - /// r(x) = (1 - p(x)) / (p(x) t) - /// $$ - /// - /// where $p(x)$ is the spot price after a long for `delta_bonds`$= x$ and - /// t is the normalized position druation. - /// - /// In this case, we use the resulting spot price after a hypothetical long - /// for `base_amount` is opened. - fn rate_after_long( - &self, - base_amount: FixedPoint, - maybe_bond_amount: Option, - ) -> Result { - let resulting_price = - self.calculate_spot_price_after_long(base_amount, maybe_bond_amount)?; - Ok((fixed!(1e18) - resulting_price) - / (resulting_price * self.annualized_position_duration())) - } - /// The derivative of the equation for calculating the rate after a long. /// /// For some $r = (1 - p(x)) / (p(x) \cdot t)$, where $p(x)$ @@ -327,7 +311,8 @@ impl State { * (inner_denominator / inner_numerator).pow(fixed!(1e18) - self.time_stretch())) } - /// Calculate the base & bond deltas from the current state given desired new reserve levels. + /// Calculate the base & bond deltas for a long trade that moves the current + /// state to the given desired ending reserve levels. /// /// Given a target ending pool share reserves, $z_t$, and bond reserves, $y_t$, /// the trade deltas to achieve that state would be: @@ -339,70 +324,17 @@ impl State { /// /// where $c$ is the vault share price and /// $c(\Delta x)$ is the (open_long_curve_fee)[long::fees::open_long_curve_fees]. - fn trade_deltas_from_reserves( + fn long_trade_deltas_from_reserves( &self, - share_reserves: FixedPoint, - bond_reserves: FixedPoint, + ending_share_reserves: FixedPoint, + ending_bond_reserves: FixedPoint, ) -> (FixedPoint, FixedPoint) { let base_delta = - (share_reserves - self.effective_share_reserves()) * self.vault_share_price(); + (ending_share_reserves - self.effective_share_reserves()) * self.vault_share_price(); let bond_delta = - (self.bond_reserves() - bond_reserves) - self.open_long_curve_fees(base_delta); + (self.bond_reserves() - ending_bond_reserves) - self.open_long_curve_fees(base_delta); (base_delta, bond_delta) } - - /// Calculates the pool reserve levels to achieve a target interest rate. - /// This calculation does not take Hyperdrive's solvency constraints or exposure - /// into account and shouldn't be used directly. - /// - /// The price for a given fixed-rate is given by $p = 1 / (r \cdot t + 1)$, where - /// $r$ is the fixed-rate and $t$ is the annualized position duration. The - /// price for a given pool reserves is given by $p = \frac{\mu z}{y}^t_{s}$, - /// where $\mu$ is the initial share price and $t_{s}$ is the time stretch - /// constant. By setting these equal we can solve for the pool reserve levels - /// as a function of a target rate. - /// - /// For some target rate, $r_t$, the pool share reserves, $z_t$, must be: - /// - /// $$ - /// z_t = \frac{1}{\mu} \left( - /// \frac{k}{\frac{c}{\mu} + \left( - /// (r_t \cdot t + 1)^{\frac{1}{t_{s}}} - /// \right)^{1 - t_{s}}} - /// \right)^{\tfrac{1}{1 - t_{s}}} - /// $$ - /// - /// and the pool bond reserves, $y_t$, must be: - /// - /// $$ - /// y_t = \left( - /// \frac{k}{ \frac{c}{\mu} + \left( - /// \left( r_t \cdot t + 1 \right)^{\frac{1}{t_{s}}} - /// \right)^{1 - t_{s}}} - /// \right)^{1 - t_{s}} \left( r_t t + 1 \right)^{\frac{1}{t_{s}}} - /// $$ - fn reserves_given_rate_ignoring_exposure>( - &self, - target_rate: F, - ) -> (FixedPoint, FixedPoint) { - let target_rate = target_rate.into(); - - // First get the target share reserves - let c_over_mu = self - .vault_share_price() - .div_up(self.initial_vault_share_price()); - let scaled_rate = (target_rate.mul_up(self.annualized_position_duration()) + fixed!(1e18)) - .pow(fixed!(1e18) / self.time_stretch()); - let inner = (self.k_down() - / (c_over_mu + scaled_rate.pow(fixed!(1e18) - self.time_stretch()))) - .pow(fixed!(1e18) / (fixed!(1e18) - self.time_stretch())); - let target_share_reserves = inner / self.initial_vault_share_price(); - - // Then get the target bond reserves. - let target_bond_reserves = inner * scaled_rate; - - (target_share_reserves, target_bond_reserves) - } } #[cfg(test)] From b756afe3f0578427bf1db8e52256ad531fd50d35 Mon Sep 17 00:00:00 2001 From: Dylan Date: Thu, 11 Apr 2024 21:38:28 -0500 Subject: [PATCH 3/7] adds calc_rate_after_short --- crates/hyperdrive-math/src/short/open.rs | 26 +++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/crates/hyperdrive-math/src/short/open.rs b/crates/hyperdrive-math/src/short/open.rs index 643cc0e25..b980549a9 100644 --- a/crates/hyperdrive-math/src/short/open.rs +++ b/crates/hyperdrive-math/src/short/open.rs @@ -2,7 +2,7 @@ use eyre::{eyre, Result}; use fixed_point::FixedPoint; use fixed_point_macros::fixed; -use crate::{State, YieldSpace}; +use crate::{calculate_rate_given_fixed_price, State, YieldSpace}; impl State { /// Calculates the amount of base the trader will need to deposit for a short of @@ -84,6 +84,30 @@ impl State { Ok(state.calculate_spot_price()) } + /// Calculate the spot rate after a short has been opened. + /// If a base_amount is not provided, then one is estimated using `calculate_open_short`. + /// + /// We calculate the rate for a fixed length of time as: + /// $$ + /// r(y) = (1 - p(y)) / (p(y) t) + /// $$ + /// + /// where $p(y)$ is the spot price after a short for `delta_bonds`$= y$ and + /// t is the normalized position druation. + /// + /// In this case, we use the resulting spot price after a hypothetical short + /// for `bond_amount` is opened. + pub fn calculate_spot_rate_after_short( + &self, + bond_amount: FixedPoint, + base_amount: Option, + ) -> FixedPoint { + calculate_rate_given_fixed_price( + self.calculate_spot_price_after_short(bond_amount, base_amount), + self.position_duration(), + ) + } + /// Calculates the amount of short principal that the LPs need to pay to back a /// short before fees are taken into consideration, $P(x)$. /// From 66c44e45870500ea075b9df84a912ea0ac042690 Mon Sep 17 00:00:00 2001 From: Dylan Date: Fri, 12 Apr 2024 16:26:52 -0700 Subject: [PATCH 4/7] fixes return types --- crates/hyperdrive-math/src/long/targeted.rs | 4 ++-- crates/hyperdrive-math/src/short/open.rs | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/hyperdrive-math/src/long/targeted.rs b/crates/hyperdrive-math/src/long/targeted.rs index 1bad8c3c0..97aca717c 100644 --- a/crates/hyperdrive-math/src/long/targeted.rs +++ b/crates/hyperdrive-math/src/long/targeted.rs @@ -125,8 +125,8 @@ impl State { "We overshot the zero-crossing during Newton's method.", )); } - // The loss is $l(x) = r(x) - r_t$ for some rate after a long - // is opened, $r(x)$, and target rate, $r_t$. + // The optimization loss can be the difference without abs or squaring + // because of the above check. let loss = resulting_rate - target_rate; // If we've done it (solvent & within error), then return the value. diff --git a/crates/hyperdrive-math/src/short/open.rs b/crates/hyperdrive-math/src/short/open.rs index b980549a9..5ae156ced 100644 --- a/crates/hyperdrive-math/src/short/open.rs +++ b/crates/hyperdrive-math/src/short/open.rs @@ -101,11 +101,12 @@ impl State { &self, bond_amount: FixedPoint, base_amount: Option, - ) -> FixedPoint { - calculate_rate_given_fixed_price( - self.calculate_spot_price_after_short(bond_amount, base_amount), + ) -> Result { + let price = self.calculate_spot_price_after_short(bond_amount, base_amount)?; + Ok(calculate_rate_given_fixed_price( + price, self.position_duration(), - ) + )) } /// Calculates the amount of short principal that the LPs need to pay to back a From 1b446d63d9d194d8ac50b4b29e4792c05fb2d9cc Mon Sep 17 00:00:00 2001 From: Dylan Paiton Date: Mon, 15 Apr 2024 11:57:59 -0700 Subject: [PATCH 5/7] fix subscript Co-authored-by: Ryan Goree --- crates/hyperdrive-math/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/hyperdrive-math/src/lib.rs b/crates/hyperdrive-math/src/lib.rs index f9735fc25..8b579f8e7 100644 --- a/crates/hyperdrive-math/src/lib.rs +++ b/crates/hyperdrive-math/src/lib.rs @@ -152,7 +152,7 @@ impl State { /// /// The price for a given fixed-rate is given by $p = 1 / (r \cdot t + 1)$, where /// $r$ is the fixed-rate and $t$ is the annualized position duration. The - /// price for a given pool reserves is given by $p = \frac{\mu z}{y}^t_{s}$, + /// price for a given pool reserves is given by $p = \frac{\mu z}{y}^{t_{s}}$, /// where $\mu$ is the initial share price and $t_{s}$ is the time stretch /// constant. By setting these equal we can solve for the pool reserve levels /// as a function of a target rate. From 34fc8fafc04d555aff0f55fd62c52856e90031ba Mon Sep 17 00:00:00 2001 From: Dylan Paiton Date: Mon, 15 Apr 2024 16:24:58 -0700 Subject: [PATCH 6/7] update docstring Co-authored-by: Ryan Goree --- crates/hyperdrive-math/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/hyperdrive-math/src/lib.rs b/crates/hyperdrive-math/src/lib.rs index 8b579f8e7..898467e94 100644 --- a/crates/hyperdrive-math/src/lib.rs +++ b/crates/hyperdrive-math/src/lib.rs @@ -164,7 +164,7 @@ impl State { /// \frac{k}{\frac{c}{\mu} + \left( /// (r_t \cdot t + 1)^{\frac{1}{t_{s}}} /// \right)^{1 - t_{s}}} - /// \right)^{\tfrac{1}{1 - t_{s}}} + /// \right)^{\frac{1}{1 - t_{s}}} /// $$ /// /// and the pool bond reserves, $y_t$, must be: From 295800e47572eee085bbb83abeeed1bb2f303305 Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 15 Apr 2024 16:25:33 -0700 Subject: [PATCH 7/7] fixes comment --- crates/hyperdrive-math/src/long/targeted.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/hyperdrive-math/src/long/targeted.rs b/crates/hyperdrive-math/src/long/targeted.rs index 97aca717c..6f635db17 100644 --- a/crates/hyperdrive-math/src/long/targeted.rs +++ b/crates/hyperdrive-math/src/long/targeted.rs @@ -125,8 +125,9 @@ impl State { "We overshot the zero-crossing during Newton's method.", )); } - // The optimization loss can be the difference without abs or squaring - // because of the above check. + // We choose the difference between the rates as the loss because it + // is convex given the above check, differentiable almost everywhere, + // and has a simple derivative. let loss = resulting_rate - target_rate; // If we've done it (solvent & within error), then return the value.