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

Zombie Interest #685

Merged
merged 20 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from 17 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
1 change: 1 addition & 0 deletions contracts/src/external/HyperdriveTarget0.sol
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ abstract contract HyperdriveTarget0 is
IHyperdrive.PoolInfo memory poolInfo = IHyperdrive.PoolInfo({
shareReserves: _marketState.shareReserves,
shareAdjustment: _marketState.shareAdjustment,
zombieShareReserves: _marketState.zombieShareReserves,
bondReserves: _marketState.bondReserves,
sharePrice: sharePrice,
longsOutstanding: _marketState.longsOutstanding,
Expand Down
6 changes: 6 additions & 0 deletions contracts/src/interfaces/IHyperdrive.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ interface IHyperdrive is IHyperdriveRead, IHyperdriveCore, IMultiToken {
/// @dev The net amount of shares that have been added and removed from
/// the share reserves due to flat updates.
int128 shareAdjustment;
/// @dev The amount shares that associated with positions that are matured,
/// but not yet redeemed.
uint128 zombieShareReserves;
jrhea marked this conversation as resolved.
Show resolved Hide resolved
/// @dev The global exposure of the pool due to open longs
uint128 longExposure;
/// @dev The amount of longs that are still open.
Expand Down Expand Up @@ -183,6 +186,9 @@ interface IHyperdrive is IHyperdriveRead, IHyperdriveCore, IMultiToken {
/// bonds. This is used to ensure that the pricing mechanism is
/// held invariant under flat updates for security reasons.
int256 shareAdjustment;
// @dev The amount shares that associated with positions that are matured,
/// but not yet redeemed.
uint256 zombieShareReserves;
/// @dev The reserves of bonds held by the pool.
uint256 bondReserves;
/// @dev The total supply of LP shares.
Expand Down
22 changes: 22 additions & 0 deletions contracts/src/internal/HyperdriveBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,28 @@ abstract contract HyperdriveBase is HyperdriveStorage {
}
}

/// @dev Collect the interest earned by closed positions
/// that haven't been redeemed.
/// @param _amount The amount in shares that earned the zombie interest.
/// @param _oldSharePrice The share price at the time of the last checkpoint.
/// @param _newSharePrice The current share price.
function _collectZombieInterest(
uint256 _amount,
uint256 _oldSharePrice,
uint256 _newSharePrice
) internal {
if (_newSharePrice > _oldSharePrice && _oldSharePrice > 0) {
// dz * (c1 - c0)/c1
uint256 zombieInterest = _amount.mulDivDown(
_newSharePrice - _oldSharePrice,
_newSharePrice
);
_marketState.zombieShareReserves -= zombieInterest.toUint128();
_marketState.shareReserves += zombieInterest.toUint128();
_marketState.shareAdjustment += int128(zombieInterest.toUint128());
jalextowle marked this conversation as resolved.
Show resolved Hide resolved
}
}

/// @dev Calculates the number of share reserves that are not reserved by
/// open positions.
/// @param _sharePrice The current share price.
Expand Down
27 changes: 23 additions & 4 deletions contracts/src/internal/HyperdriveCheckpoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ abstract contract HyperdriveCheckpoint is
// Create the share price checkpoint.
checkpoint_.sharePrice = _sharePrice.toUint128();

// Collect the interest that has accrued since the last checkpoint.
_collectZombieInterest(
_marketState.zombieShareReserves,
_checkpoints[_checkpointTime - _checkpointDuration].sharePrice,
_sharePrice
jrhea marked this conversation as resolved.
Show resolved Hide resolved
);

// Close out all of the short positions that matured at the beginning of
// this checkpoint. This ensures that shorts don't continue to collect
// free variable interest and that LP's can withdraw the proceeds of
Expand Down Expand Up @@ -117,6 +124,18 @@ abstract contract HyperdriveCheckpoint is
int256(shareProceeds), // keep the effective share reserves constant
_checkpointTime
);
uint256 shareReservesDelta = maturedShortsAmount.divDown(
_sharePrice
);
uint256 shareProceeds = HyperdriveMath.calculateShortProceeds(
maturedShortsAmount,
shareReservesDelta,
openSharePrice,
_sharePrice,
_sharePrice,
_flatFee
);
_marketState.zombieShareReserves += shareProceeds.toUint128();
positionsClosed = true;
}

Expand Down Expand Up @@ -146,6 +165,7 @@ abstract contract HyperdriveCheckpoint is
int256(shareProceeds), // keep the effective share reserves constant
jrhea marked this conversation as resolved.
Show resolved Hide resolved
checkpointTime
);
_marketState.zombieShareReserves += shareProceeds.toUint128();
positionsClosed = true;
}

Expand Down Expand Up @@ -189,15 +209,14 @@ abstract contract HyperdriveCheckpoint is
return _sharePrice;
}

/// @dev Calculates the proceeds of the long holders of a given position at
/// maturity. The long holders will be the LPs if the position is a
/// short.
/// @dev Calculates the proceeds of the holders of a given position at
/// maturity.
/// @param _bondAmount The bond amount of the position.
/// @param _sharePrice The current share price.
/// @param _openSharePrice The share price at the beginning of the
/// position's checkpoint.
/// @param _isLong A flag indicating whether or not the position is a long.
/// @return shareProceeds The proceeds of the long holders in shares.
/// @return shareProceeds The proceeds of the holders in shares.
/// @return governanceFee The fee paid to governance in shares.
function _calculateMaturedProceeds(
uint256 _bondAmount,
Expand Down
17 changes: 17 additions & 0 deletions contracts/src/internal/HyperdriveLong.sol
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,23 @@ abstract contract HyperdriveLong is HyperdriveLP {

// Distribute the excess idle to the withdrawal pool.
_distributeExcessIdle(sharePrice);
} else {
// The user is redeeming a long that has already matured. So we
// collect the interest that has accrued since the last checkpoint.
// NOTE: We only collect the interest on the position that is being closed.
uint256 checkpointTime = _latestCheckpoint();
_collectZombieInterest(
shareProceeds,
_checkpoints[checkpointTime].sharePrice,
sharePrice
);
uint256 zombieShareReserves = _marketState.zombieShareReserves;
if (shareProceeds < zombieShareReserves) {
zombieShareReserves -= shareProceeds;
} else {
zombieShareReserves = 0;
}
_marketState.zombieShareReserves = zombieShareReserves.toUint128();
}

// Withdraw the profit to the trader.
Expand Down
19 changes: 18 additions & 1 deletion contracts/src/internal/HyperdriveShort.sol
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,9 @@ abstract contract HyperdriveShort is HyperdriveLP {
// If the position hasn't matured, apply the accounting updates that
// result from closing the short to the reserves and pay out the
// withdrawal pool if necessary.
uint256 bondAmount = _bondAmount; // Avoid stack too deep error.
uint256 maturityTime = _maturityTime; // Avoid stack too deep error.
uint256 sharePrice_ = sharePrice; // Avoid stack too deep error.
uint256 bondAmount = _bondAmount; // Avoid stack too deep error.
if (block.timestamp < maturityTime) {
// Attribute the governance fees.
_governanceFeesAccrued += totalGovernanceFee;
Expand Down Expand Up @@ -202,6 +202,23 @@ abstract contract HyperdriveShort is HyperdriveLP {

// Distribute the excess idle to the withdrawal pool.
_distributeExcessIdle(sharePrice_);
} else {
// The user is redeeming a short that has already matured. So we
// collect the interest that has accrued since the last checkpoint.
// NOTE: We only collect the interest on the position that is being closed.
uint256 checkpointTime = _latestCheckpoint();
_collectZombieInterest(
shareProceeds,
_checkpoints[checkpointTime].sharePrice,
sharePrice
);
uint256 zombieShareReserves = _marketState.zombieShareReserves;
if (shareProceeds < zombieShareReserves) {
zombieShareReserves -= shareProceeds;
} else {
zombieShareReserves = 0;
}
_marketState.zombieShareReserves = zombieShareReserves.toUint128();
}

// Withdraw the profit to the trader. This includes the proceeds from
Expand Down
10 changes: 4 additions & 6 deletions contracts/src/libraries/HyperdriveMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -659,14 +659,12 @@ library HyperdriveMath {
uint256 _flatFee
) internal pure returns (uint256 shareProceeds) {
// If the interest is more negative than the trading profits and margin
// released, than the short proceeds are marked to zero. Otherwise, we
// released, then the short proceeds are marked to zero. Otherwise, we
// calculate the proceeds as the sum of the trading proceeds, the
// interest proceeds, and the margin released.
uint256 bondFactor = _bondAmount.mulDivDown(
_closeSharePrice,
// We round up here do avoid overestimating the share proceeds.
_openSharePrice.mulUp(_sharePrice)
);
uint256 bondFactor = _bondAmount
.mulDivDown(_closeSharePrice, _openSharePrice)
.divDown(_sharePrice);

// We increase the bondFactor by the flat fee amount, because the trader
// has provided the flat fee as margin, and so it must be returned to
Expand Down
1 change: 1 addition & 0 deletions crates/hyperdrive-math/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ impl Distribution<State> for Standard {
let share_reserves = rng.gen_range(fixed!(1_000e18)..=fixed!(100_000_000e18));
let info = PoolInfo {
share_reserves: share_reserves.into(),
zombie_share_reserves: fixed!(0).into(),
bond_reserves: rng
.gen_range(
share_reserves * FixedPoint::from(config.initial_share_price)
Expand Down
12 changes: 7 additions & 5 deletions crates/hyperdrive-math/src/short/close.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ impl State {
share_price: FixedPoint,
flat_fee: FixedPoint,
) -> FixedPoint {
let mut bond_factor = bond_amount.mul_div_down(
close_share_price,
// We round up here do avoid overestimating the share proceeds.
open_share_price.mul_up(share_price),
);
let mut bond_factor = bond_amount
.mul_div_down(
close_share_price,
// We round up here do avoid overestimating the share proceeds.
open_share_price,
)
.div_down(share_price);
bond_factor += bond_amount.mul_div_down(flat_fee, share_price);

if bond_factor > share_amount {
Expand Down
5 changes: 5 additions & 0 deletions crates/test-utils/src/crash_reports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ impl From<RawPoolConfig> for PoolConfig {
struct RawPoolInfo {
share_reserves: u128,
share_adjustment: i128,
zombie_share_reserves: u128,
bond_reserves: u128,
lp_total_supply: u128,
share_price: u128,
Expand All @@ -126,6 +127,7 @@ impl From<RawPoolInfo> for PoolInfo {
Self {
share_reserves: r.share_reserves.into(),
share_adjustment: r.share_adjustment.into(),
zombie_share_reserves: r.zombie_share_reserves.into(),
bond_reserves: r.bond_reserves.into(),
lp_total_supply: r.lp_total_supply.into(),
share_price: r.share_price.into(),
Expand Down Expand Up @@ -300,6 +302,7 @@ mod tests {
"raw_pool_info": {
"shareReserves": 100000000000000000000000000,
"shareAdjustment": 0,
"zombieShareReserves": 0,
"bondReserves": 102178995195337961200000000,
"lpTotalSupply": 99999990000000000000000000,
"sharePrice": 1000000006341958396,
Expand Down Expand Up @@ -374,6 +377,7 @@ mod tests {
"pool_info": {
"shareReserves": "100000000.0",
"shareAdjustment": "0.0",
"zombieShareReserves": "0.0",
"bondReserves": "102178995.1953379612",
"lpTotalSupply": "99999990.0",
"sharePrice": "1.000000006341958396",
Expand Down Expand Up @@ -451,6 +455,7 @@ mod tests {
pool_info: PoolInfo {
share_reserves: uint256!(100000000000000000000000000),
share_adjustment: int256!(0),
zombie_share_reserves: uint256!(0),
bond_reserves: uint256!(102178995195337961200000000),
lp_total_supply: uint256!(99999990000000000000000000),
share_price: uint256!(1000000006341958396),
Expand Down
14 changes: 13 additions & 1 deletion test/integrations/hyperdrive/IntraCheckpointNettingTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,7 @@ contract IntraCheckpointNettingTest is HyperdriveTest {
// longExposure should be 0
IHyperdrive.PoolInfo memory poolInfo = hyperdrive.getPoolInfo();
assertApproxEqAbs(poolInfo.longExposure, 0, 1);

// idle should be equal to shareReserves
uint256 expectedShareReserves = MockHyperdrive(address(hyperdrive))
.calculateIdleShareReserves(hyperdrive.getPoolInfo().sharePrice) +
Expand Down Expand Up @@ -732,6 +733,7 @@ contract IntraCheckpointNettingTest is HyperdriveTest {

// fast forward time and accrue interest
advanceTime(POSITION_DURATION, variableInterest);
hyperdrive.checkpoint(HyperdriveUtils.latestCheckpoint(hyperdrive));

// open positions
uint256[] memory longMaturityTimes = new uint256[](numTrades);
Expand Down Expand Up @@ -764,7 +766,7 @@ contract IntraCheckpointNettingTest is HyperdriveTest {
advanceTimeWithCheckpoints(timeElapsed, variableInterest);

// remove liquidity
removeLiquidity(alice, aliceLpShares);
(, uint256 withdrawalShares) = removeLiquidity(alice, aliceLpShares);

// Ensure all the positions have matured before trying to close them.
IHyperdrive.PoolInfo memory poolInfo = hyperdrive.getPoolInfo();
Expand All @@ -783,6 +785,7 @@ contract IntraCheckpointNettingTest is HyperdriveTest {
// close the long positions
closeLong(bob, longMaturityTimes[i], bondAmounts[i]);
}
redeemWithdrawalShares(alice, withdrawalShares);

// longExposure should be 0
poolInfo = hyperdrive.getPoolInfo();
Expand All @@ -807,11 +810,13 @@ contract IntraCheckpointNettingTest is HyperdriveTest {
{
uint256 apr = 0.05e18;
deploy(alice, apr, initialSharePrice, 0, 0, 0);
// JR TODO: we should add this as a parameter to fuzz to ensure that we are solvent with withdrawal shares
uint256 contribution = 500_000_000e18;
aliceLpShares = initialize(alice, apr, contribution);

// fast forward time and accrue interest
advanceTime(POSITION_DURATION, variableInterest);
hyperdrive.checkpoint(HyperdriveUtils.latestCheckpoint(hyperdrive));
}

// open positions
Expand Down Expand Up @@ -852,6 +857,13 @@ contract IntraCheckpointNettingTest is HyperdriveTest {
// close the long positions
closeLong(bob, longMaturityTimes[i], bondAmounts[i]);
}
poolInfo = hyperdrive.getPoolInfo();

// TODO: Enable this. It fails for test_netting_extreme_negative_interest_time_elapsed
jrhea marked this conversation as resolved.
Show resolved Hide resolved
// (uint256 withdrawalProceeds, ) = redeemWithdrawalShares(
// alice,
// withdrawalShares
// );

// longExposure should be 0
poolInfo = hyperdrive.getPoolInfo();
Expand Down
Loading
Loading