Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f2c19ae
Made some progress on the negative interest issues
jalextowle Sep 26, 2023
9552e23
Simplified the mature negative interest flow and fixed the flow for s…
jalextowle Sep 27, 2023
3e374bd
Fixed a discrepancy in the negative interest logic in `closeLong`
jalextowle Sep 27, 2023
41458a9
Cleanup
jalextowle Sep 27, 2023
8a9ce3a
Fixed issue after merge
jalextowle Sep 28, 2023
2c97455
Refactored out `sharePaymentWithoutFees`
jalextowle Sep 28, 2023
3b1f73c
Refactored out `shareProceedsWithFees`
jalextowle Sep 28, 2023
cd10625
Addressed review feedback from @jrhea
jalextowle Sep 29, 2023
29fda0e
"Fixed" an intermittent failure in `test_lp_withdrawal_long_and_short…
jalextowle Sep 29, 2023
500b083
Commit intermmediate progress
jalextowle Sep 28, 2023
af25c00
Made the share adjustment fixes
jalextowle Sep 28, 2023
e3b281d
Added a `minSharePrice`
jalextowle Sep 28, 2023
6525ba4
Fixed some tests
jalextowle Sep 28, 2023
ef62e6c
Fixed the Rust codebase
jalextowle Sep 29, 2023
2ee5ee0
Name changes in `HyperdriveLong`
jalextowle Sep 29, 2023
91d7b79
Removed the long share price and did some renaming in `HyperdriveShort`
jalextowle Sep 29, 2023
a5cecc9
De-duplicated the negative interest logic between `closeLong` and `cl…
jalextowle Sep 29, 2023
f6691bc
Renamed `shareCurvePayment` and `shareCurveProceeds` to `shareCurveDe…
jalextowle Sep 29, 2023
0717377
Merge branch 'main' into jalextowle/bug/negative-interest-mature-posi…
jalextowle Sep 29, 2023
0950f34
Negative Interest: Round Trip (#600)
jalextowle Sep 29, 2023
91f7839
Merge remote-tracking branch 'origin/jalextowle/bug/negative-interest…
jalextowle Sep 29, 2023
9ac1a15
Renamed `bondCurvePayment` and `bondCurveProceeds` to `bondCurveDelta`
jalextowle Sep 30, 2023
ae98c71
Merge remote-tracking branch 'origin/main' into jalextowle/negative-i…
jalextowle Sep 30, 2023
f06bd6b
Fixed issues after merge
jalextowle Sep 30, 2023
0a03cc7
Fixed the path independence test and added a test for k invariance
jalextowle Oct 3, 2023
ab8ca86
Merge remote-tracking branch 'origin/main' into jalextowle/negative-i…
jalextowle Oct 6, 2023
657f370
Addressed review feedback
jalextowle Oct 6, 2023
4c432f0
Merge branch 'jalextowle/negative-interest/refactor' into jalextowle/…
jalextowle Oct 6, 2023
6631214
Merge remote-tracking branch 'origin/main' into jalextowle/negative-i…
jalextowle Oct 9, 2023
595fb7e
Cleaned up the comments
jalextowle Oct 9, 2023
6d7db94
Fixed a bug in `openShort` caused by negative interest
jalextowle Oct 10, 2023
9e80e96
Added negative interest to `test_path_independence`
jalextowle Oct 10, 2023
7a6cdcf
Ignored the max short integration test
jalextowle Oct 10, 2023
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: 0 additions & 4 deletions contracts/src/HyperdriveLong.sol
Original file line number Diff line number Diff line change
Expand Up @@ -452,10 +452,6 @@ abstract contract HyperdriveLong is HyperdriveLP {
);
}

// FIXME: We should calculate the share adjustment here. There is a
// component of the share adjustment needed for negative interest on the
// curve and another for flat updates.
//
/// @dev Calculate the pool reserve and trader deltas that result from
/// closing a long. This calculation includes trading fees.
/// @param _bondAmount The amount of bonds being purchased to close the short.
Expand Down
17 changes: 5 additions & 12 deletions contracts/src/HyperdriveShort.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,6 @@ abstract contract HyperdriveShort is HyperdriveLP {
maturityTime = latestCheckpoint + _positionDuration;
uint256 shareReservesDelta;
{
// FIXME: After thinking about this a bit, I think that negative
// interest makes it possible to open shorts for free. We should
// test this edge case as it will cause issues.
//
// TODO: What happens to the short deposit if the open share price
// is greater than the current share price?
uint256 totalGovernanceFee;
(
traderDeposit,
Expand Down Expand Up @@ -436,7 +430,10 @@ abstract contract HyperdriveShort is HyperdriveLP {

// The trader will need to deposit capital to pay for the fixed rate,
// the curve fee, the flat fee, and any back-paid interest that will be
// received back upon closing the trade.
// received back upon closing the trade. If negative interest has
// accrued during the current checkpoint, we set close share price to
// equal the open share price. This ensures that shorts don't benefit
// from negative interest that accrued during the current checkpoint.
traderDeposit = HyperdriveMath
.calculateShortProceeds(
_bondAmount,
Expand All @@ -445,7 +442,7 @@ abstract contract HyperdriveShort is HyperdriveLP {
// their deposit.
shareReservesDelta - totalGovernanceFee,
_openSharePrice,
_sharePrice,
_sharePrice.max(_openSharePrice),
_sharePrice,
_flatFee
)
Expand All @@ -454,10 +451,6 @@ abstract contract HyperdriveShort is HyperdriveLP {
return (traderDeposit, shareReservesDelta, totalGovernanceFee);
}

// FIXME: We should calculate the share adjustment here. There is a
// component of the share adjustment needed for negative interest on the
// curve and another for flat updates.
//
/// @dev Calculate the pool reserve and trader deltas that result from
/// closing a short. This calculation includes trading fees.
/// @param _bondAmount The amount of bonds being purchased to close the
Expand Down
4 changes: 0 additions & 4 deletions contracts/src/libraries/HyperdriveMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -335,10 +335,6 @@ library HyperdriveMath {
// shareCurveDelta
int256 shareAdjustmentDelta;
if (_closeSharePrice < _openSharePrice) {
// TODO: It may be better to scale here considering that the current
// negative interest logic creates dangerous situations in
// `openShort`.
//
// We only need to scale the proceeds in the case that we're closing
// a long since `calculateShortProceeds` accounts for negative
// interest.
Expand Down
1 change: 1 addition & 0 deletions crates/hyperdrive-math/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ async fn preamble(

// TODO: Unignore after we add the logic to apply checkpoints prior to computing
// the max long.
#[ignore]
#[tokio::test]
pub async fn test_integration_get_max_short() -> Result<()> {
// Set up a random number generator. We use ChaCha8Rng with a randomly
Expand Down
123 changes: 117 additions & 6 deletions test/integrations/hyperdrive/PresentValueTest.t.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.19;

import { console } from "forge-std/console.sol";
import { console2 as console } from "forge-std/console2.sol";
import { AssetId } from "contracts/src/libraries/AssetId.sol";
import { FixedPointMath } from "contracts/src/libraries/FixedPointMath.sol";
import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol";
Expand Down Expand Up @@ -525,6 +525,10 @@ contract PresentValueTest is HyperdriveTest {
}

function test_present_value(bytes32 __seed) external {
// TODO: It would be better to bound all of the intermediate present values
// to the starting present value instead of bounding to the previous present
// value.
//
// TODO: This tolerance is WAY too large.
uint256 tolerance = 1_000_000e18;

Expand Down Expand Up @@ -649,7 +653,7 @@ contract PresentValueTest is HyperdriveTest {
POSITION_DURATION.mulDown(0.99e18)
);
int256 variableRate = int256(uint256(seed())).normalizeToRange(
0,
-0.5e18,
1e18
);
advanceTime(timeDelta, variableRate);
Expand Down Expand Up @@ -742,11 +746,118 @@ contract PresentValueTest is HyperdriveTest {
info1 = hyperdrive.getPoolInfo();
}

// Ensure that the reserves are approximately the same across the two
// rounds of trading.
assertApproxEqAbs(info0.shareReserves, info1.shareReserves, 1e12);
// Ensure that the ending YieldSpace coordinates are approximately
// equal. The ending share reserves and share adjustment may not match
// because the negative interest component of the share adjustment is
// path dependent.
assertApproxEqAbs(
Comment thread
jalextowle marked this conversation as resolved.
HyperdriveMath.calculateEffectiveShareReserves(
info0.shareReserves,
info0.shareAdjustment
),
HyperdriveMath.calculateEffectiveShareReserves(
info1.shareReserves,
info1.shareAdjustment
),
1e12
);
assertApproxEqAbs(info0.bondReserves, info1.bondReserves, 1e12);
assertApproxEqAbs(info0.shareAdjustment, info1.shareAdjustment, 1e12);
}

function test_k_invariance(bytes32 __seed) external {
uint256 tolerance = 1e12;

// Set the seed.
_seed = __seed;

// Initialize the pool.
initialize(alice, 0.02e18, 500_000_000e18);

// Accrues positive interest for a period.
advanceTime(hyperdrive.getPoolConfig().positionDuration, 1e18);

// Execute a series of random open trades. We ensure that k remains
// invariant throughout the trading.
uint256 k = hyperdrive.k();
uint256 maturityTime0 = hyperdrive.maturityTimeFromLatestCheckpoint();
Trade[] memory trades0 = randomOpenTrades();
for (uint256 i = 0; i < trades0.length; i++) {
executeTrade(trades0[i]);
assertApproxEqAbs(hyperdrive.k(), k, tolerance);
k = hyperdrive.k();
}

// Time passes and interest accrues.
{
uint256 timeDelta = uint256(seed()).normalizeToRange(
CHECKPOINT_DURATION,
POSITION_DURATION.mulDown(0.99e18)
);
int256 variableRate = int256(uint256(seed())).normalizeToRange(
-0.2e18,
1e18
);
advanceTime(timeDelta, variableRate);
}

// Execute a series of random open trades. We ensure that k remains
// invariant throughout the trading.
k = hyperdrive.k();
uint256 maturityTime1 = hyperdrive.maturityTimeFromLatestCheckpoint();
Trade[] memory trades1 = randomOpenTrades();
for (uint256 i = 0; i < trades1.length; i++) {
executeTrade(trades1[i]);
assertApproxEqAbs(hyperdrive.k(), k, tolerance);
k = hyperdrive.k();
}

// Close all of the positions in a random order and verify that k is
// invariant throughout the trading.
Trade[] memory closeTrades;
{
Trade[] memory closeTrades0 = randomCloseTrades(
maturityTime0,
hyperdrive.balanceOf(
AssetId.encodeAssetId(
AssetId.AssetIdPrefix.Long,
maturityTime0
),
alice
),
maturityTime0,
hyperdrive.balanceOf(
AssetId.encodeAssetId(
AssetId.AssetIdPrefix.Short,
maturityTime0
),
alice
)
);
Trade[] memory closeTrades1 = randomCloseTrades(
maturityTime1,
hyperdrive.balanceOf(
AssetId.encodeAssetId(
AssetId.AssetIdPrefix.Long,
maturityTime1
),
alice
),
maturityTime1,
hyperdrive.balanceOf(
AssetId.encodeAssetId(
AssetId.AssetIdPrefix.Short,
maturityTime1
),
alice
)
);
closeTrades = combineTrades(closeTrades0, closeTrades1);
}
for (uint256 i = 0; i < closeTrades.length; i++) {
executeTrade(closeTrades[i]);
assertApproxEqAbs(hyperdrive.k(), k, tolerance);
k = hyperdrive.k();
}
}

/// Random Trading ///
Expand Down
64 changes: 41 additions & 23 deletions test/units/hyperdrive/OpenShortTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -159,29 +159,6 @@ contract OpenShortTest is HyperdriveTest {
);
}

function test_RevertsWithNegativeInterestRate() public {
uint256 apr = 0.05e18;

// Initialize the pool with a large amount of capital.
uint256 contribution = 500_000_000e18;
initialize(alice, apr, contribution);

vm.stopPrank();
vm.startPrank(bob);

uint256 bondAmount = (hyperdrive.calculateMaxShort() * 90) / 100;
openShort(bob, bondAmount);

uint256 longAmount = (hyperdrive.calculateMaxLong() * 50) / 100;
openLong(bob, longAmount);

//vm.expectRevert(IHyperdrive.NegativeInterest.selector);

uint256 baseAmount = (hyperdrive.calculateMaxShort() * 100) / 100;
openShort(bob, baseAmount);
//I think we could trigger this with big short, open long, and short?
}

function test_governance_fees_excluded_share_reserves() public {
uint256 apr = 0.05e18;
uint256 contribution = 500_000_000e18;
Expand Down Expand Up @@ -289,6 +266,47 @@ contract OpenShortTest is HyperdriveTest {
assertEq(basePaid, basePaid2);
}

function test_open_short_after_negative_interest(
int256 variableRate
) external {
// Alice initializes the pool.
uint256 fixedRate = 0.05e18;
uint256 contribution = 500_000_000e18;
initialize(alice, fixedRate, contribution);

// Get the deposit amount for a short opened with no negative interest.
uint256 expectedBasePaid;
uint256 snapshotId = vm.snapshot();
uint256 shortAmount = 100_000e18;
{
hyperdrive.checkpoint(hyperdrive.latestCheckpoint());
advanceTime(
hyperdrive.getPoolConfig().checkpointDuration.mulDown(0.5e18),
0
);
(, expectedBasePaid) = openShort(bob, shortAmount);
}
vm.revertTo(snapshotId);

// Get the deposit amount for a short opened with negative interest.
variableRate = variableRate.normalizeToRange(-100e18, 0);
uint256 basePaid;
{
hyperdrive.checkpoint(hyperdrive.latestCheckpoint());
advanceTime(
hyperdrive.getPoolConfig().checkpointDuration.mulDown(0.5e18),
variableRate
);
(, basePaid) = openShort(bob, shortAmount);
}

// The base paid should be greater than or equal (with a fudge factor)
// to the base paid with no negative interest. In theory, we'd like the
// deposits to be equal, but the trading calculation changes slightly
// with negative interest due to rounding.
assertGe(basePaid + 1e9, expectedBasePaid);
}

function verifyOpenShort(
IHyperdrive.PoolInfo memory poolInfoBefore,
uint256 contribution,
Expand Down
18 changes: 17 additions & 1 deletion test/utils/HyperdriveUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity 0.8.19;

import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol";
import { AssetId } from "contracts/src/libraries/AssetId.sol";
import { FixedPointMath } from "contracts/src/libraries/FixedPointMath.sol";
import { FixedPointMath, ONE } from "contracts/src/libraries/FixedPointMath.sol";
import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol";
import { YieldSpaceMath } from "contracts/src/libraries/YieldSpaceMath.sol";

Expand Down Expand Up @@ -368,6 +368,22 @@ library HyperdriveUtils {
int256(config.minimumShareReserves);
}

function k(IHyperdrive hyperdrive) internal view returns (uint256) {
IHyperdrive.PoolConfig memory config = hyperdrive.getPoolConfig();
IHyperdrive.PoolInfo memory info = hyperdrive.getPoolInfo();
return
YieldSpaceMath.modifiedYieldSpaceConstant(
info.sharePrice.divDown(config.initialSharePrice),
config.initialSharePrice,
HyperdriveMath.calculateEffectiveShareReserves(
info.shareReserves,
info.shareAdjustment
),
ONE - config.timeStretch,
info.bondReserves
);
}

function decodeError(
bytes memory _error
) internal pure returns (string memory) {
Expand Down