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
76 changes: 49 additions & 27 deletions contracts/Hyperdrive.sol
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,11 @@ abstract contract Hyperdrive is MultiToken, IHyperdrive {
// Initialize the base token address.
baseToken = _baseToken;

// Initialize the time configurations.
// Initialize the time configurations. There must be at least one
// checkpoint per term to avoid having a position duration of zero.
if (_checkpointsPerTerm == 0) {
revert Errors.InvalidCheckpointsPerTerm();
}
positionDuration = _checkpointsPerTerm * _checkpointDuration;
checkpointDuration = _checkpointDuration;
timeStretch = _timeStretch;
Expand Down Expand Up @@ -246,18 +250,29 @@ abstract contract Hyperdrive is MultiToken, IHyperdrive {
timeStretch
);

// Calculate the amount of LP shares that the supplier should receive.
lpShares = HyperdriveMath.calculateLpSharesOutForSharesIn(
shares,
shareReserves,
totalSupply[AssetId._LP_ASSET_ID],
// To ensure that our LP allocation scheme fairly rewards LPs for adding
// liquidity, we linearly interpolate between the present and future
// value of longs and shorts. These interpolated values are the long and
// short adjustments. The following calculation is used to determine the
// amount of LP shares rewarded to new LP:
//
// lpShares = (dz * l) / (z + a_s - a_l)
uint256 longAdjustment = HyperdriveMath.calculateLpAllocationAdjustment(
longsOutstanding,
shortsOutstanding,
longBaseVolume,
_calculateTimeRemaining(longAverageMaturityTime),
sharePrice
);

// Enforce min user outputs
if (_minOutput > lpShares) revert Errors.OutputLimit();
uint256 shortAdjustment = HyperdriveMath
.calculateLpAllocationAdjustment(
shortsOutstanding,
shortBaseVolume,
_calculateTimeRemaining(shortAverageMaturityTime),
sharePrice
);
lpShares = shares.mulDown(totalSupply[AssetId._LP_ASSET_ID]).divDown(
shareReserves.add(shortAdjustment).sub(longAdjustment)
);

// Update the reserves.
shareReserves += shares;
Expand All @@ -270,12 +285,13 @@ abstract contract Hyperdrive is MultiToken, IHyperdrive {
timeStretch
);

// Enforce min user outputs
if (_minOutput > lpShares) revert Errors.OutputLimit();

// Mint LP shares to the supplier.
_mint(AssetId._LP_ASSET_ID, _destination, lpShares);
}

// TODO: Consider if some MEV protection is necessary for the LP.
//
/// @notice Allows an LP to burn shares and withdraw from the pool.
/// @param _shares The LP shares to burn.
/// @param _minOutput The minium amount of the base token to receive. Note - this
Expand Down Expand Up @@ -360,8 +376,10 @@ abstract contract Hyperdrive is MultiToken, IHyperdrive {

// Withdraw the shares from the yield source.
(uint256 baseOutput, ) = withdraw(shareProceeds, _destination);

// Enforce min user outputs
if (_minOutput > baseOutput) revert Errors.OutputLimit();

return (baseOutput, longWithdrawalShares, shortWithdrawalShares);
}

Expand Down Expand Up @@ -489,7 +507,8 @@ abstract contract Hyperdrive is MultiToken, IHyperdrive {
poolBondDelta,
sharePrice,
latestCheckpoint,
maturityTime
maturityTime,
timeRemaining
);

// Mint the bonds to the trader with an ID of the maturity time.
Expand Down Expand Up @@ -684,15 +703,14 @@ abstract contract Hyperdrive is MultiToken, IHyperdrive {
true
);

// TODO: Think about backdating more. This is only correct when the time
// remaining is one.
//
// Update the base volume of short positions.
{
uint256 baseAmount = shareProceeds.mulDown(openSharePrice);
shortBaseVolume += baseAmount;
shortBaseVolumeCheckpoints[latestCheckpoint] += baseAmount;
}
uint256 baseVolume = HyperdriveMath.calculateBaseVolume(
shareProceeds.mulDown(openSharePrice),
_bondAmount,
timeRemaining
);
shortBaseVolume += baseVolume;
shortBaseVolumeCheckpoints[latestCheckpoint] += baseVolume;

// Apply the trading deltas to the reserves and increase the bond buffer
// by the amount of bonds that were shorted. We don't need to add the
Expand Down Expand Up @@ -941,14 +959,16 @@ abstract contract Hyperdrive is MultiToken, IHyperdrive {
/// @param _sharePrice The share price.
/// @param _checkpointTime The time of the latest checkpoint.
/// @param _maturityTime The maturity time of the long.
/// @param _timeRemaining The time remaining until maturity.
function _applyOpenLong(
uint256 _baseAmount,
uint256 _shareAmount,
uint256 _bondProceeds,
uint256 _poolBondDelta,
uint256 _sharePrice,
uint256 _checkpointTime,
uint256 _maturityTime
uint256 _maturityTime,
uint256 _timeRemaining
) internal {
// Update the average maturity time of long positions.
longAverageMaturityTime = longAverageMaturityTime.updateWeightedAverage(
Expand All @@ -958,12 +978,14 @@ abstract contract Hyperdrive is MultiToken, IHyperdrive {
true
);

// TODO: Think about backdating more. This is only correct when the time
// remaining is one.
//
// Update the base volume of long positions.
longBaseVolume += _baseAmount;
longBaseVolumeCheckpoints[_checkpointTime] += _baseAmount;
uint256 baseVolume = HyperdriveMath.calculateBaseVolume(
_baseAmount,
_bondProceeds,
_timeRemaining
);
longBaseVolume += baseVolume;
longBaseVolumeCheckpoints[_checkpointTime] += baseVolume;

// Apply the trading deltas to the reserves and update the amount of
// longs outstanding.
Expand Down
1 change: 1 addition & 0 deletions contracts/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ library Errors {
error BaseBufferExceedsShareReserves();
error InvalidCheckpointTime();
error InvalidCheckpointDuration();
error InvalidCheckpointsPerTerm();
error InvalidMaturityTime();
error PoolAlreadyInitialized();
error TransferFailed();
Expand Down
76 changes: 52 additions & 24 deletions contracts/libraries/HyperdriveMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -487,32 +487,60 @@ library HyperdriveMath {
}
}

// TODO: Use an allocation scheme that doesn't punish early LPs.
//
/// @dev Calculates the amount of LP shares that should be awarded for
/// supplying a specified amount of base shares to the pool.
/// @param _shares The amount of base shares supplied to the pool.
/// @param _shareReserves The pool's share reserves.
/// @param _lpTotalSupply The pool's total supply of LP shares.
/// @param _longsOutstanding The amount of long positions outstanding.
/// @param _shortsOutstanding The amount of short positions outstanding.
/// @dev Calculates the base volume of an open trade given the base amount,
/// the bond amount, and the time remaining. Since the base amount
/// takes into account backdating, we can't use this as our base
/// volume. Since we linearly interpolate between the base volume
/// and the bond amount as the time remaining goes from 1 to 0, the
/// base volume is can be determined as follows:
///
/// baseAmount = t * baseVolume + (1 - t) * bondAmount
/// =>
/// baseVolume = (baseAmount - (1 - t) * bondAmount) / t
/// @param _baseAmount The base exchanged in the open trade.
/// @param _bondAmount The bonds exchanged in the open trade.
/// @param _timeRemaining The time remaining in the position.
/// @return baseVolume The calculated base volume.
function calculateBaseVolume(
uint256 _baseAmount,
uint256 _bondAmount,
uint256 _timeRemaining
) internal pure returns (uint256 baseVolume) {
// If the time remaining is 0, the position has already matured and
// doesn't have an impact on LP's ability to withdraw. This is a
// pathological case that should never arise.
if (_timeRemaining == 0) return 0;
baseVolume = (
_baseAmount.sub(
(FixedPointMath.ONE_18.sub(_timeRemaining)).mulDown(_bondAmount)
)
).divDown(_timeRemaining);
return baseVolume;
}

/// @dev Computes the LP allocation adjustment for a position. This is used
/// to accurately account for the duration risk that LPs take on when
/// adding liquidity so that LP shares can be rewarded fairly.
/// @param _positionsOutstanding The position balance outstanding.
/// @param _baseVolume The base volume created by opening the positions.
/// @param _averageTimeRemaining The average time remaining of the positions.
/// @param _sharePrice The pool's share price.
/// @return The amount of LP shares awarded.
function calculateLpSharesOutForSharesIn(
uint256 _shares,
uint256 _shareReserves,
uint256 _lpTotalSupply,
uint256 _longsOutstanding,
uint256 _shortsOutstanding,
/// @return adjustment The allocation adjustment.
function calculateLpAllocationAdjustment(
uint256 _positionsOutstanding,
uint256 _baseVolume,
uint256 _averageTimeRemaining,
uint256 _sharePrice
) internal pure returns (uint256) {
// (dz * l) / (z + b_y / c - b_x / c)
return
_shares.mulDown(_lpTotalSupply).divDown(
_shareReserves.add(_shortsOutstanding.divDown(_sharePrice)).sub(
_longsOutstanding.divDown(_sharePrice)
)
);
) internal pure returns (uint256 adjustment) {
// baseAdjustment = t * _baseVolume + (1 - t) * _positionsOutstanding
adjustment = (_averageTimeRemaining.mulDown(_baseVolume)).add(
(FixedPointMath.ONE_18.sub(_averageTimeRemaining)).mulDown(
_positionsOutstanding
)
);
// adjustment = baseAdjustment / c
adjustment = adjustment.divDown(_sharePrice);
return adjustment;
}

/// @dev Calculates the amount of base shares released from burning a
Expand Down
19 changes: 0 additions & 19 deletions test/mocks/MockHyperdriveMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -220,25 +220,6 @@ contract MockHyperdriveMath {
return (result1, result2);
}

function calculateLpSharesOutForSharesIn(
uint256 _shares,
uint256 _shareReserves,
uint256 _lpTotalSupply,
uint256 _longsOutstanding,
uint256 _shortsOutstanding,
uint256 _sharePrice
) external pure returns (uint256) {
uint256 result = HyperdriveMath.calculateLpSharesOutForSharesIn(
_shares,
_shareReserves,
_lpTotalSupply,
_longsOutstanding,
_shortsOutstanding,
_sharePrice
);
return result;
}

function calculateOutForLpSharesIn(
uint256 _shares,
uint256 _shareReserves,
Expand Down
Loading