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
2 changes: 2 additions & 0 deletions contracts/src/instances/erc4626/ERC4626Base.sol
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ abstract contract ERC4626Base is HyperdriveBase {
uint256 _sharePrice,
IHyperdrive.Options calldata _options
) internal override returns (uint256 amountWithdrawn) {
// NOTE: Round down to underestimate the base proceeds.
//
// Correct for any error that crept into the calculation of the share
// amount by converting the shares to base and then back to shares
// using the vault's share conversion logic.
Expand Down
2 changes: 2 additions & 0 deletions contracts/src/instances/steth/StETHBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ abstract contract StETHBase is HyperdriveBase {
revert IHyperdrive.UnsupportedToken();
}

// NOTE: Round down to underestimate the base proceeds.
//
// Correct for any error that crept into the calculation of the share
// amount by converting the shares to base and then back to shares
// using the vault's share conversion logic.
Expand Down
48 changes: 41 additions & 7 deletions contracts/src/internal/HyperdriveBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,9 @@ abstract contract HyperdriveBase is HyperdriveStorage {
timeRemaining = _maturityTime > latestCheckpoint
? _maturityTime - latestCheckpoint
: 0;
timeRemaining = (timeRemaining).divDown(_positionDuration);

// NOTE: Round down to underestimate the time remaining.
timeRemaining = timeRemaining.divDown(_positionDuration);
}

/// @dev Calculates the normalized time remaining of a position when the
Expand All @@ -206,7 +208,9 @@ abstract contract HyperdriveBase is HyperdriveStorage {
timeRemaining = _maturityTime > latestCheckpoint
? _maturityTime - latestCheckpoint
: 0;
timeRemaining = (timeRemaining).divDown(_positionDuration * ONE);

// NOTE: Round down to underestimate the time remaining.
timeRemaining = timeRemaining.divDown(_positionDuration * ONE);
}

/// @dev Gets the most recent checkpoint time.
Expand Down Expand Up @@ -277,8 +281,10 @@ abstract contract HyperdriveBase is HyperdriveStorage {
uint256 startingPresentValue = LPMath.calculatePresentValue(
presentValueParams
);
// NOTE: For consistency with the present value calculation, we round
// up the long side and round down the short side.
int256 netCurveTrade = int256(
presentValueParams.longsOutstanding.mulDown(
presentValueParams.longsOutstanding.mulUp(
presentValueParams.longAverageTimeRemaining
)
) -
Expand Down Expand Up @@ -355,9 +361,11 @@ abstract contract HyperdriveBase is HyperdriveStorage {
/// @return True if the share reserves are greater than the exposure plus
/// the minimum share reserves.
function _isSolvent(uint256 _vaultSharePrice) internal view returns (bool) {
// NOTE: Round the lhs up and the rhs down to make the check more
// conservative.
return
int256(
(uint256(_marketState.shareReserves).mulDown(_vaultSharePrice))
(uint256(_marketState.shareReserves).mulUp(_vaultSharePrice))
) -
int128(_marketState.longExposure) >=
int256(_minimumShareReserves.mulDown(_vaultSharePrice));
Expand Down Expand Up @@ -397,6 +405,8 @@ abstract contract HyperdriveBase is HyperdriveStorage {
uint256 zombieBaseReserves
) = _collectZombieInterest(_vaultSharePrice);

// NOTE: Round down to underestimate the proceeds.
//
// If negative interest has accrued in the zombie reserves, we
// discount the share proceeds in proportion to the amount of
// negative interest that has accrued.
Expand Down Expand Up @@ -439,6 +449,8 @@ abstract contract HyperdriveBase is HyperdriveStorage {
internal
returns (uint256 zombieBaseProceeds, uint256 zombieBaseReserves)
{
// NOTE: Round down to underestimate the proceeds.
//
// Get the zombie base proceeds and reserves.
zombieBaseReserves = _vaultSharePrice.mulDown(
_marketState.zombieShareReserves
Expand All @@ -452,11 +464,17 @@ abstract contract HyperdriveBase is HyperdriveStorage {
// difference between the base reserves and the base proceeds.
uint256 zombieInterest = zombieBaseReserves - zombieBaseProceeds;

// NOTE: Round up to overestimate the impact that removing the
// interest had on the zombie share reserves.
//
// Remove the zombie interest from the zombie share reserves.
_marketState.zombieShareReserves -= zombieInterest
.divUp(_vaultSharePrice)
.toUint128();

// NOTE: Round down to underestimate the zombhie interest given to
// the LPs and governance.
//
// Calculate and collect the governance fee.
// The fee is calculated in terms of shares and paid to
// governance.
Expand Down Expand Up @@ -492,7 +510,8 @@ abstract contract HyperdriveBase is HyperdriveStorage {
function _calculateIdleShareReserves(
uint256 _vaultSharePrice
) internal view returns (uint256 idleShares) {
uint256 longExposure = uint256(_marketState.longExposure).divDown(
// NOTE: Round up to underestimate the pool's idle.
uint256 longExposure = uint256(_marketState.longExposure).divUp(
_vaultSharePrice
);
if (_marketState.shareReserves > longExposure + _minimumShareReserves) {
Expand All @@ -510,6 +529,7 @@ abstract contract HyperdriveBase is HyperdriveStorage {
function _calculateLPSharePrice(
uint256 _vaultSharePrice
) internal view returns (uint256 lpSharePrice) {
// NOTE: Round down to underestimate the LP share price.
uint256 presentValue = _vaultSharePrice > 0
? LPMath
.calculatePresentValue(_getPresentValueParams(_vaultSharePrice))
Expand All @@ -519,7 +539,7 @@ abstract contract HyperdriveBase is HyperdriveStorage {
_totalSupply[AssetId._WITHDRAWAL_SHARE_ASSET_ID] -
_withdrawPool.readyToWithdraw;
lpSharePrice = lpTotalSupply == 0
? 0
? 0 // NOTE: Round down to underestimate the LP share price.
: presentValue.divDown(lpTotalSupply);
return lpSharePrice;
}
Expand All @@ -537,6 +557,8 @@ abstract contract HyperdriveBase is HyperdriveStorage {
uint256 _spotPrice,
uint256 _vaultSharePrice
) internal view returns (uint256 curveFee, uint256 governanceCurveFee) {
// NOTE: Round down to underestimate the curve fee.
//
// Fixed Rate (r) = (value at maturity - purchase price)/(purchase price)
// = (1-p)/p
// = ((1 / p) - 1)
Expand All @@ -561,6 +583,8 @@ abstract contract HyperdriveBase is HyperdriveStorage {
.mulDown(_vaultSharePrice)
.mulDown(_shareAmount);

// NOTE: Round down to underestimate the governance curve fee.
//
// We leave the governance fee in terms of bonds:
// governanceCurveFee = curve_fee * p * phi_gov
// = bonds * phi_gov
Expand Down Expand Up @@ -593,10 +617,12 @@ abstract contract HyperdriveBase is HyperdriveStorage {
uint256 totalGovernanceFee
)
{
// NOTE: Round down to underestimate the curve fee.
//
// p (spot price) tells us how many base a bond is worth -> p = base/bonds
// 1 - p tells us how many additional base a bond is worth at
// maturity -> (1 - p) = additional base/bonds

//
// The curve fee is taken from the additional base the user gets for
// each bond at maturity:
//
Expand All @@ -610,12 +636,16 @@ abstract contract HyperdriveBase is HyperdriveStorage {
.mulDown(_bondAmount)
.mulDivDown(_normalizedTimeRemaining, _vaultSharePrice);

// NOTE: Round down to underestimate the governance curve fee.
//
// Calculate the curve portion of the governance fee:
//
// governanceCurveFee = curve_fee * phi_gov
// = shares * phi_gov
governanceCurveFee = curveFee.mulDown(_governanceLPFee);

// NOTE: Round down to underestimate the flat fee.
//
// The flat portion of the fee is taken from the matured bonds.
// Since a matured bond is worth 1 base, it is appropriate to consider
// d_y in units of base:
Expand All @@ -630,6 +660,8 @@ abstract contract HyperdriveBase is HyperdriveStorage {
);
flatFee = flat.mulDown(_flatFee);

// NOTE: Round down to underestimate the total governance fee.
//
// We calculate the flat portion of the governance fee as:
//
// governance_flat_fee = flat_fee * phi_gov
Expand All @@ -655,6 +687,7 @@ abstract contract HyperdriveBase is HyperdriveStorage {
if (_options.asBase) {
return _amount;
} else {
// NOTE: Round down to underestimate the base amount.
return _amount.mulDown(_vaultSharePrice);
}
}
Expand All @@ -672,6 +705,7 @@ abstract contract HyperdriveBase is HyperdriveStorage {
if (_options.asBase) {
return _amount;
} else {
// NOTE: Round down to underestimate the shares amount.
return _amount.divDown(_vaultSharePrice);
}
}
Expand Down
14 changes: 12 additions & 2 deletions contracts/src/internal/HyperdriveCheckpoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -126,17 +126,20 @@ abstract contract HyperdriveCheckpoint is
int256(shareProceeds), // keep the effective share reserves constant
_checkpointTime
);
uint256 shareReservesDelta = maturedShortsAmount.divDown(
// NOTE: Round up to underestimate the short proceeds.
uint256 shareReservesDelta = maturedShortsAmount.divUp(
_vaultSharePrice
);
shareProceeds = HyperdriveMath.calculateShortProceeds(
// NOTE: Round down to underestimate the short proceeds.
shareProceeds = HyperdriveMath.calculateShortProceedsDown(
maturedShortsAmount,
shareReservesDelta,
openVaultSharePrice,
_vaultSharePrice,
_vaultSharePrice,
_flatFee
);
// NOTE: Round down to underestimate the short proceeds.
_marketState.zombieBaseProceeds += shareProceeds
.mulDown(_vaultSharePrice)
.toUint112();
Expand Down Expand Up @@ -170,6 +173,7 @@ abstract contract HyperdriveCheckpoint is
int256(shareProceeds), // keep the effective share reserves constant
checkpointTime
);
// NOTE: Round down to underestimate the long proceeds.
_marketState.zombieBaseProceeds += shareProceeds
.mulDown(_vaultSharePrice)
.toUint112();
Expand Down Expand Up @@ -223,6 +227,9 @@ abstract contract HyperdriveCheckpoint is
// Calculate the share proceeds, flat fee, and governance fee. Since the
// position is closed at maturity, the share proceeds are equal to the
// bond amount divided by the vault share price.
//
// NOTE: Round down to underestimate the share proceeds, flat fee, and
// governance fee.
shareProceeds = _bondAmount.divDown(_vaultSharePrice);
uint256 flatFee = shareProceeds.mulDown(_flatFee);
governanceFee = flatFee.mulDown(_governanceLPFee);
Expand All @@ -249,10 +256,13 @@ abstract contract HyperdriveCheckpoint is
// governance fee are given a "haircut" proportional to the negative
// interest that accrued.
if (_vaultSharePrice < _openVaultSharePrice) {
// NOTE: Round down to underestimate the proceeds.
shareProceeds = shareProceeds.mulDivDown(
_vaultSharePrice,
_openVaultSharePrice
);

// NOTE: Round down to underestimate the governance fee.
governanceFee = governanceFee.mulDivDown(
_vaultSharePrice,
_openVaultSharePrice
Expand Down
12 changes: 10 additions & 2 deletions contracts/src/internal/HyperdriveLP.sol
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ abstract contract HyperdriveLP is HyperdriveBase, HyperdriveMultiToken {
params.bondReserves = _marketState.bondReserves;
endingPresentValue = LPMath.calculatePresentValue(params);

// NOTE: Round down to underestimate the amount of LP shares minted.
//
// The LP shares minted to the LP is derived by solving for the
// change in LP shares that preserves the ratio of present value to
// total LP shares. This ensures that LPs are fairly rewarded for
Expand All @@ -201,6 +203,8 @@ abstract contract HyperdriveLP is HyperdriveBase, HyperdriveMultiToken {
}
}

// NOTE: Round down to make the check more conservative.
//
// Enforce the minimum LP share price slippage guard.
if (_contribution.divDown(lpShares) < _minLpSharePrice) {
revert IHyperdrive.InvalidLpSharePrice();
Expand All @@ -214,7 +218,7 @@ abstract contract HyperdriveLP is HyperdriveBase, HyperdriveMultiToken {

// Emit an AddLiquidity event.
uint256 lpSharePrice = lpTotalSupply == 0
? 0
? 0 // NOTE: We always round the LP share price down for consistency.
: startingPresentValue.divDown(lpTotalSupply);
uint256 baseContribution = _convertToBaseFromOption(
_contribution,
Expand Down Expand Up @@ -387,6 +391,8 @@ abstract contract HyperdriveLP is HyperdriveBase, HyperdriveMultiToken {
withdrawalSharesRedeemed
);

// NOTE: Round down to underestimate the share proceeds.
//
// The LP gets the pro-rata amount of the collected proceeds.
uint128 proceeds_ = _withdrawPool.proceeds;
uint256 shareProceeds = withdrawalSharesRedeemed.mulDivDown(
Expand All @@ -403,8 +409,10 @@ abstract contract HyperdriveLP is HyperdriveBase, HyperdriveMultiToken {
// Withdraw the share proceeds to the user.
proceeds = _withdraw(shareProceeds, _sharePrice, _options);

// NOTE: Round up to make the check more conservative.
//
// Enforce the minimum user output per share.
if (_minOutputPerShare.mulDown(withdrawalSharesRedeemed) > proceeds) {
if (_minOutputPerShare.mulUp(withdrawalSharesRedeemed) > proceeds) {
revert IHyperdrive.OutputLimit();
}

Expand Down
5 changes: 5 additions & 0 deletions contracts/src/internal/HyperdriveLong.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ abstract contract HyperdriveLong is HyperdriveLP {
// against the minimum transaction amount because in the event of
// slippage on the deposit, we want the inputs to the state updates to
// respect the minimum transaction amount requirements.
//
// NOTE: Round down to underestimate the base deposit. This makes the
// minimum transaction amount check more conservative.
uint256 baseDeposited = sharesDeposited.mulDown(vaultSharePrice);
if (baseDeposited < _minimumTransactionAmount) {
revert IHyperdrive.MinimumTransactionAmount();
Expand Down Expand Up @@ -425,6 +428,8 @@ abstract contract HyperdriveLong is HyperdriveLP {
// bonds = bonds + bonds
bondReservesDelta = bondProceeds + governanceCurveFee;

// NOTE: Round down to underestimate the governance fee.
//
// Calculate the fees owed to governance in shares. Open longs are
// calculated entirely on the curve so the curve fee is the total
// governance fee. In order to convert it to shares we need to multiply
Expand Down
14 changes: 10 additions & 4 deletions contracts/src/internal/HyperdriveShort.sol
Original file line number Diff line number Diff line change
Expand Up @@ -369,10 +369,12 @@ abstract contract HyperdriveShort is HyperdriveLP {
_initialVaultSharePrice
);

// NOTE: Round up to make the check stricter.
//
// If the base proceeds of selling the bonds is greater than the bond
// amount, then the trade occurred in the negative interest domain. We
// revert in these pathological cases.
if (shareReservesDelta.mulDown(_vaultSharePrice) > _bondAmount) {
if (shareReservesDelta.mulUp(_vaultSharePrice) > _bondAmount) {
revert IHyperdrive.NegativeInterest();
}

Expand Down Expand Up @@ -410,6 +412,8 @@ abstract contract HyperdriveShort is HyperdriveLP {
// shares -= shares - shares
shareReservesDelta -= curveFee - governanceCurveFee;

// NOTE: Round up to overestimate the base deposit.
//
// 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. If negative interest has
Expand All @@ -418,7 +422,7 @@ abstract contract HyperdriveShort is HyperdriveLP {
// don't benefit from negative interest that accrued during the current
// checkpoint.
baseDeposit = HyperdriveMath
.calculateShortProceeds(
.calculateShortProceedsUp(
_bondAmount,
// NOTE: We add the governance fee back to the share reserves
// delta here because the trader will need to provide this in
Expand All @@ -429,7 +433,7 @@ abstract contract HyperdriveShort is HyperdriveLP {
_vaultSharePrice,
_flatFee
)
.mulDown(_vaultSharePrice);
.mulUp(_vaultSharePrice);

return (baseDeposit, shareReservesDelta, governanceCurveFee);
}
Expand Down Expand Up @@ -556,14 +560,16 @@ abstract contract HyperdriveShort is HyperdriveLP {
? _vaultSharePrice
: _checkpoints[_maturityTime].vaultSharePrice;

// NOTE: Round down to underestimate the short proceeds.
//
// Calculate the share proceeds owed to the short. We calculate this
// before scaling the share payment for negative interest. Shorts
// are responsible for paying for 100% of the negative interest, so
// they aren't benefited when the payment to LPs is decreased due to
// negative interest. Similarly, the governance fee is included in
// the share payment. The LPs don't receive the governance fee, but
// the short is responsible for paying it.
shareProceeds = HyperdriveMath.calculateShortProceeds(
shareProceeds = HyperdriveMath.calculateShortProceedsDown(
_bondAmount,
shareReservesDelta,
openVaultSharePrice,
Expand Down
Loading