Skip to content
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
4 changes: 4 additions & 0 deletions contracts/src/interfaces/IHyperdrive.sol
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,10 @@ interface IHyperdrive is
/// int128 scale.
error UnsafeCastToInt128();

/// @notice Thrown when casting a value to a int256 that is outside of the
/// int256 scale.
error UnsafeCastToInt256();

/// @notice Thrown when an unsupported option is passed to a function or
/// a user attempts to sweep an invalid token. The options and sweep
/// targets that are supported vary between instances.
Expand Down
47 changes: 19 additions & 28 deletions contracts/src/libraries/HyperdriveMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,40 +38,31 @@ library HyperdriveMath {
);
timeStretch = ONE.divDown(timeStretch);

// If the position duration is 1 year, we can return the benchmark.
if (_positionDuration == 365 days) {
return timeStretch;
}

// Otherwise, we need to adjust the time stretch to account for the
// position duration. We do this by holding the reserve ratio constant
// and solving for the new time stretch directly.
// We know that the following simultaneous equations hold:
//
// We can calculate the spot price at the target apr and position
// duration as:
// (1 + apr) * A ** timeStretch = 1
//
// p = 1 / (1 + apr * (positionDuration / 365 days))
// and
//
// We then calculate the benchmark reserve ratio, `ratio`, implied by
// the benchmark time stretch using the `calculateInitialBondReserves`
// function.
// (1 + apr * (positionDuration / 365 days)) * A ** targetTimeStretch = 1
//
// We can then derive the adjusted time stretch using the spot price
// calculation:
// where A is the reserve ratio. We can solve these equations for the
// target time stretch as follows:
//
// p = ratio ** timeStretch
// =>
// timeStretch = ln(p) / ln(ratio)
uint256 targetSpotPrice = ONE.divDown(
ONE + _apr.mulDivDown(_positionDuration, 365 days)
);
uint256 benchmarkReserveRatio = ONE.divDown(
calculateInitialBondReserves(ONE, ONE, _apr, 365 days, timeStretch)
);
// targetTimeStretch = (
// ln(1 + apr * (positionDuration / 365 days)) /
// ln(1 + apr)
// ) * timeStretch
//
// NOTE: Round down so that the output is an underestimate.
return
uint256(-int256(targetSpotPrice).ln()).divDown(
uint256(-int256(benchmarkReserveRatio).ln())
);
(
uint256(
(ONE + _apr.mulDivDown(_positionDuration, 365 days))
.toInt256()
.ln()
).divDown(uint256((ONE + _apr).toInt256().ln()))
).mulDown(timeStretch);
}

/// @dev Calculates the spot price of bonds in terms of base. This
Expand Down
10 changes: 10 additions & 0 deletions contracts/src/libraries/SafeCast.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,14 @@ library SafeCast {
}
y = int128(x);
}

/// @notice This function safely casts an uint256 to an int256.
/// @param x The uint256 to cast to int256.
/// @return y The int256 casted from x.
function toInt256(uint256 x) internal pure returns (int256 y) {
if (!(x <= uint256(type(int256).max))) {
revert IHyperdrive.UnsafeCastToInt256();
}
y = int256(x);
}
}
50 changes: 16 additions & 34 deletions crates/hyperdrive-math/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,46 +9,28 @@ pub fn get_time_stretch(rate: FixedPoint, position_duration: FixedPoint) -> Fixe
let time_stretch = fixed!(5.24592e18)
/ (fixed!(0.04665e18) * FixedPoint::from(U256::from(rate) * uint256!(100)));
let time_stretch = fixed!(1e18) / time_stretch;
// if the position duration is 1 year, we can return the benchmark
if position_duration == seconds_in_a_year {
return time_stretch;
}

// Otherwise, we need to adjust the time stretch to account for the
// position duration. We do this by holding the reserve ratio constant
// and solving for the new time stretch directly.
// We know that the following simultaneous equations hold:
//
// (1 + apr) * A ** timeStretch = 1
//
// We can calculate the spot price at the target apr and position
// duration as:
// and
//
// p = 1 / (1 + apr * (positionDuration / 365 days))
// (1 + apr * (positionDuration / 365 days)) * A ** targetTimeStretch = 1
//
// We then calculate the benchmark reserve ratio, `ratio`, implied by
// the benchmark time stretch using the `calculateInitialBondReserves`
// function.
// where A is the reserve ratio. We can solve these equations for the
// target time stretch as follows:
//
// We can then derive the adjusted time stretch using the spot price
// calculation:
// targetTimeStretch = (
// ln(1 + apr * (positionDuration / 365 days)) /
// ln(1 + apr)
// ) * timeStretch
//
// p = ratio ** timeStretch
// =>
// timeStretch = ln(p) / ln(ratio)
let target_spot_price =
fixed!(1e18) / (fixed!(1e18) + rate.mul_div_down(position_duration, seconds_in_a_year));
let benchmark_reserve_ratio = fixed!(1e18)
/ calculate_initial_bond_reserves(
fixed!(1e18),
fixed!(1e18),
rate,
seconds_in_a_year,
time_stretch,
);
// target spot price and benchmark reserve ratio will have negative ln,
// but since we are dividing them we can cast to positive before converting types
// TODO: implement FixedPoint `neg` pub fn to support "-"
let new_time_stretch = FixedPoint::from(-FixedPoint::ln(I256::from(target_spot_price)))
/ FixedPoint::from(-FixedPoint::ln(I256::from(benchmark_reserve_ratio)));
new_time_stretch
// NOTE: Round down so that the output is an underestimate.
(FixedPoint::from(FixedPoint::ln(I256::from(
fixed!(1e18) + rate.mul_div_down(position_duration, seconds_in_a_year),
))) / FixedPoint::from(FixedPoint::ln(I256::from(fixed!(1e18) + rate))))
* time_stretch
}

pub fn get_effective_share_reserves(
Expand Down