From 0bb571cf27ccdc72af0b583bafed653ac1139fe0 Mon Sep 17 00:00:00 2001 From: Sheng Lundquist Date: Mon, 15 Apr 2024 13:12:49 -0700 Subject: [PATCH 1/5] Fixing bug in average time remaining calculation in present value Co-authored-by: Dylan Paiton --- crates/hyperdrive-math/src/lib.rs | 4 ++-- crates/hyperdrive-math/src/lp.rs | 34 +++++++++++++++++++++++-------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/crates/hyperdrive-math/src/lib.rs b/crates/hyperdrive-math/src/lib.rs index 8308628f8..6f27cfecb 100644 --- a/crates/hyperdrive-math/src/lib.rs +++ b/crates/hyperdrive-math/src/lib.rs @@ -89,10 +89,10 @@ impl Distribution for Standard { } }, long_average_maturity_time: rng - .gen_range(fixed!(0)..=FixedPoint::from(60 * 60 * 24 * 365)) + .gen_range(fixed!(0)..=FixedPoint::from(60 * 60 * 24 * 365) * fixed!(1e18)) .into(), short_average_maturity_time: rng - .gen_range(fixed!(0)..=FixedPoint::from(60 * 60 * 24 * 365)) + .gen_range(fixed!(0)..=FixedPoint::from(60 * 60 * 24 * 365) * fixed!(1e18)) .into(), lp_total_supply: rng .gen_range(fixed!(1_000e18)..=fixed!(100_000_000e18)) diff --git a/crates/hyperdrive-math/src/lp.rs b/crates/hyperdrive-math/src/lp.rs index f42533475..7788b5a3b 100644 --- a/crates/hyperdrive-math/src/lp.rs +++ b/crates/hyperdrive-math/src/lp.rs @@ -175,14 +175,32 @@ impl State { /// Calculates the present value of LPs capital in the pool. pub fn calculate_present_value(&self, current_block_timestamp: U256) -> FixedPoint { // Calculate the average time remaining for the longs and shorts. - let long_average_time_remaining = self.calculate_normalized_time_remaining( - self.long_average_maturity_time().into(), - current_block_timestamp, - ); - let short_average_time_remaining = self.calculate_normalized_time_remaining( - self.short_average_maturity_time().into(), - current_block_timestamp, - ); + + // To keep precision of long and short average maturity time (from contract call) + // we scale the block timestamp and position duration by 1e18 to calculate + // the normalized time remaining. + let scaled_latest_checkpoint = + FixedPoint::from(self.to_checkpoint(current_block_timestamp)) * fixed!(1e18); + let scaled_position_duration = self.position_duration() * fixed!(1e18); + let long_average_maturity_time = self.long_average_maturity_time(); + let short_average_maturity_time = self.short_average_maturity_time(); + + let long_average_time_remaining = if long_average_maturity_time > scaled_latest_checkpoint { + // NOTE: Round down to underestimate the time remaining. + FixedPoint::from(long_average_maturity_time - scaled_latest_checkpoint) + .div_down(scaled_position_duration) + } else { + fixed!(0) + }; + + let short_average_time_remaining = if short_average_maturity_time > scaled_latest_checkpoint + { + // NOTE: Round down to underestimate the time remaining. + FixedPoint::from(short_average_maturity_time - scaled_latest_checkpoint) + .div_down(scaled_position_duration) + } else { + fixed!(0) + }; let present_value: I256 = I256::try_from(self.share_reserves()).unwrap() + self.calculate_net_curve_trade( From e52da1fcdb99d7e8be9c7ae76e9b684e8a8a8341 Mon Sep 17 00:00:00 2001 From: Sheng Lundquist Date: Mon, 15 Apr 2024 13:34:49 -0700 Subject: [PATCH 2/5] Binding the upper bound of average maturtiy time by the position duration Co-authored-by: Dylan Paiton --- crates/hyperdrive-math/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/hyperdrive-math/src/lib.rs b/crates/hyperdrive-math/src/lib.rs index 6f27cfecb..491415852 100644 --- a/crates/hyperdrive-math/src/lib.rs +++ b/crates/hyperdrive-math/src/lib.rs @@ -89,10 +89,10 @@ impl Distribution for Standard { } }, long_average_maturity_time: rng - .gen_range(fixed!(0)..=FixedPoint::from(60 * 60 * 24 * 365) * fixed!(1e18)) + .gen_range(fixed!(0)..=FixedPoint::from(config.position_duration) * fixed!(1e18)) .into(), short_average_maturity_time: rng - .gen_range(fixed!(0)..=FixedPoint::from(60 * 60 * 24 * 365) * fixed!(1e18)) + .gen_range(fixed!(0)..=FixedPoint::from(config.position_duration) * fixed!(1e18)) .into(), lp_total_supply: rng .gen_range(fixed!(1_000e18)..=fixed!(100_000_000e18)) From d6bfc90307e2d601e5d38eed5bf3605a3ddfe70d Mon Sep 17 00:00:00 2001 From: Sheng Lundquist Date: Mon, 15 Apr 2024 13:40:19 -0700 Subject: [PATCH 3/5] Undoing upper bound on maturity time --- crates/hyperdrive-math/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/hyperdrive-math/src/lib.rs b/crates/hyperdrive-math/src/lib.rs index 491415852..973d1d42a 100644 --- a/crates/hyperdrive-math/src/lib.rs +++ b/crates/hyperdrive-math/src/lib.rs @@ -88,11 +88,13 @@ impl Distribution for Standard { .unwrap() } }, + // If this range returns greater than position duration, then both rust and solidity will fail + // on calls that depend on this value. long_average_maturity_time: rng - .gen_range(fixed!(0)..=FixedPoint::from(config.position_duration) * fixed!(1e18)) + .gen_range(fixed!(0)..=FixedPoint::from(365 * one_day_in_seconds) * fixed!(1e18)) .into(), short_average_maturity_time: rng - .gen_range(fixed!(0)..=FixedPoint::from(config.position_duration) * fixed!(1e18)) + .gen_range(fixed!(0)..=FixedPoint::from(365 * one_day_in_seconds) * fixed!(1e18)) .into(), lp_total_supply: rng .gen_range(fixed!(1_000e18)..=fixed!(100_000_000e18)) From 4e21c377ea5f7dd36cc56d342403d57bce41f4da Mon Sep 17 00:00:00 2001 From: Sheng Lundquist Date: Mon, 15 Apr 2024 13:55:50 -0700 Subject: [PATCH 4/5] Fixing scaling values Co-authored-by: Dylan Paiton --- crates/hyperdrive-math/src/lp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/hyperdrive-math/src/lp.rs b/crates/hyperdrive-math/src/lp.rs index 7788b5a3b..7465787f4 100644 --- a/crates/hyperdrive-math/src/lp.rs +++ b/crates/hyperdrive-math/src/lp.rs @@ -180,8 +180,8 @@ impl State { // we scale the block timestamp and position duration by 1e18 to calculate // the normalized time remaining. let scaled_latest_checkpoint = - FixedPoint::from(self.to_checkpoint(current_block_timestamp)) * fixed!(1e18); - let scaled_position_duration = self.position_duration() * fixed!(1e18); + FixedPoint::from(self.to_checkpoint(current_block_timestamp)) * fixed!(1e36); + let scaled_position_duration = self.position_duration() * fixed!(1e36); let long_average_maturity_time = self.long_average_maturity_time(); let short_average_maturity_time = self.short_average_maturity_time(); From 64849860575f24cf7d2bbf1f317538eae578aaba Mon Sep 17 00:00:00 2001 From: Sheng Lundquist Date: Mon, 15 Apr 2024 14:09:21 -0700 Subject: [PATCH 5/5] Abstracting time calculation and using it in test when passing to solidity --- crates/hyperdrive-math/src/lp.rs | 57 +++++++++++++++++--------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/crates/hyperdrive-math/src/lp.rs b/crates/hyperdrive-math/src/lp.rs index 7465787f4..a2c2373b3 100644 --- a/crates/hyperdrive-math/src/lp.rs +++ b/crates/hyperdrive-math/src/lp.rs @@ -172,35 +172,40 @@ impl State { idle_shares_in_base } - /// Calculates the present value of LPs capital in the pool. - pub fn calculate_present_value(&self, current_block_timestamp: U256) -> FixedPoint { - // Calculate the average time remaining for the longs and shorts. - - // To keep precision of long and short average maturity time (from contract call) - // we scale the block timestamp and position duration by 1e18 to calculate - // the normalized time remaining. + /// Function that takes in a scaled FixedPoint maturity time and calculates + /// normalized time remaining with higher precision. + fn calculate_scaled_normalized_time_remaining( + &self, + scaled_maturity_time: FixedPoint, + current_time: U256, + ) -> FixedPoint { let scaled_latest_checkpoint = - FixedPoint::from(self.to_checkpoint(current_block_timestamp)) * fixed!(1e36); + FixedPoint::from(self.to_checkpoint(current_time)) * fixed!(1e36); let scaled_position_duration = self.position_duration() * fixed!(1e36); - let long_average_maturity_time = self.long_average_maturity_time(); - let short_average_maturity_time = self.short_average_maturity_time(); - - let long_average_time_remaining = if long_average_maturity_time > scaled_latest_checkpoint { + if scaled_maturity_time > scaled_latest_checkpoint { // NOTE: Round down to underestimate the time remaining. - FixedPoint::from(long_average_maturity_time - scaled_latest_checkpoint) + FixedPoint::from(scaled_maturity_time - scaled_latest_checkpoint) .div_down(scaled_position_duration) } else { fixed!(0) - }; + } + } - let short_average_time_remaining = if short_average_maturity_time > scaled_latest_checkpoint - { - // NOTE: Round down to underestimate the time remaining. - FixedPoint::from(short_average_maturity_time - scaled_latest_checkpoint) - .div_down(scaled_position_duration) - } else { - fixed!(0) - }; + /// Calculates the present value of LPs capital in the pool. + pub fn calculate_present_value(&self, current_block_timestamp: U256) -> FixedPoint { + // Calculate the average time remaining for the longs and shorts. + + // To keep precision of long and short average maturity time (from contract call) + // we scale the block timestamp and position duration by 1e18 to calculate + // the normalized time remaining. + let long_average_time_remaining = self.calculate_scaled_normalized_time_remaining( + self.long_average_maturity_time(), + current_block_timestamp, + ); + let short_average_time_remaining = self.calculate_scaled_normalized_time_remaining( + self.short_average_maturity_time(), + current_block_timestamp, + ); let present_value: I256 = I256::try_from(self.share_reserves()).unwrap() + self.calculate_net_curve_trade( @@ -550,14 +555,14 @@ mod tests { minimum_share_reserves: state.config.minimum_share_reserves, minimum_transaction_amount: state.config.minimum_transaction_amount, long_average_time_remaining: state - .calculate_normalized_time_remaining( - state.long_average_maturity_time().into(), + .calculate_scaled_normalized_time_remaining( + state.long_average_maturity_time(), current_block_timestamp.into(), ) .into(), short_average_time_remaining: state - .calculate_normalized_time_remaining( - state.short_average_maturity_time().into(), + .calculate_scaled_normalized_time_remaining( + state.short_average_maturity_time(), current_block_timestamp.into(), ) .into(),