Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
58edbb5
Started writing the checkpointing system
jalextowle Feb 9, 2023
bb09b5a
Wrote checkpointing logic without making it zombie-proof
jalextowle Feb 9, 2023
64aeba6
Fixed changes after rebase
jalextowle Feb 10, 2023
ca84704
Added accounting for paying out matured longs to the withdrawal pool
jalextowle Feb 10, 2023
4679172
Updated the close long accounting for matured longs
jalextowle Feb 10, 2023
a1ae339
Updated the close short accounting for matured shorts
jalextowle Feb 11, 2023
fed7fa5
Adds a naive external checkpointing flow
jalextowle Feb 11, 2023
d45a731
Made the external checkpointing flow more robust
jalextowle Feb 11, 2023
e465e0f
Removed extra data from the asset ID system
jalextowle Feb 11, 2023
d1cf43e
Adds a redemption flow for the withdrawal shares
jalextowle Feb 11, 2023
a320f18
Fixed a bug in the "_checkpoint" function
jalextowle Feb 12, 2023
4870635
Fixed a bug in the checkpointing flow
jalextowle Feb 12, 2023
d11364a
Fixed some issues found during testing
jalextowle Feb 12, 2023
9e31428
Simplify the matured positions accounting
jalextowle Feb 13, 2023
089318d
Wrote a test for initialize
jalextowle Feb 11, 2023
1246601
Improved the initialization tests
jalextowle Feb 11, 2023
c5cc90b
Added tests for opening longs
jalextowle Feb 12, 2023
6ae5e3a
Updated the pool's initialization logic
jalextowle Feb 12, 2023
223957a
Add some failure tests for "closeLong"
jalextowle Feb 12, 2023
99ccebe
Wrote simple tests for "closeLong" and fixed bugs
jalextowle Feb 12, 2023
a17bd40
Adds simple test cases for "openShort"
jalextowle Feb 13, 2023
7bd1f1f
Fixed "test_close_long_redeem"
jalextowle Feb 13, 2023
cf1706f
Added some close short tests
jalextowle Feb 13, 2023
9aad0fd
Added checkpointing to a few lingering places
jalextowle Feb 13, 2023
e16b5df
Addressed review feedback from @jrhea, @aleph_v, and @Padraic-O-Mhuiris
jalextowle Feb 14, 2023
9b55bb7
Addressed more review feedback from @aleph_v
jalextowle Feb 14, 2023
d5b9de4
Merge remote-tracking branch 'origin/main' into jalextowle/hyperdrive…
jalextowle Feb 14, 2023
9c99323
Fixed errors after merge
jalextowle Feb 14, 2023
f4aa3e9
Merge remote-tracking branch 'origin/main' into jalextowle/hyperdrive…
jalextowle Feb 14, 2023
10a0b33
Merge remote-tracking branch 'origin/jalextowle/hyperdrive/share-pric…
jalextowle Feb 14, 2023
f761bd2
don't call YieldSpace math in calculateSharesInGivenBondsOut() when t…
jrhea Feb 15, 2023
83e931a
run prettier
jrhea Feb 15, 2023
c31c5ea
Fixed stack too deep issues after rebase
jalextowle Feb 14, 2023
07f26fb
Merge remote-tracking branch 'origin/main' into jalextowle/tests/simp…
jalextowle Feb 15, 2023
c067a43
Fixed failures after merge
jalextowle Feb 15, 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
90 changes: 81 additions & 9 deletions contracts/Hyperdrive.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { IHyperdrive } from "contracts/interfaces/IHyperdrive.sol";
/// @custom:disclaimer The language used in this code is for coding convenience
/// only, and is not intended to, and does not, have any
/// particular legal or regulatory significance.
contract Hyperdrive is MultiToken, IHyperdrive {
abstract contract Hyperdrive is MultiToken, IHyperdrive {
using FixedPointMath for uint256;

/// Tokens ///
Expand Down Expand Up @@ -107,15 +107,14 @@ contract Hyperdrive is MultiToken, IHyperdrive {
}

/// Yield Source ///
// In order to deploy a yield source implement must be written which implements the following methods

/// @notice Transfers base from the user and commits it to the yield source.
/// @param amount The amount of base to deposit.
/// @return sharesMinted The shares this deposit creates.
/// @return sharePrice The share price at time of deposit.
function deposit(
uint256 amount
) internal virtual returns (uint256 sharesMinted, uint256 sharePrice) {}
) internal virtual returns (uint256 sharesMinted, uint256 sharePrice);

/// @notice Withdraws shares from the yield source and sends the base
/// released to the destination.
Expand All @@ -126,11 +125,11 @@ contract Hyperdrive is MultiToken, IHyperdrive {
function withdraw(
uint256 shares,
address destination
) internal virtual returns (uint256 amountWithdrawn, uint256 sharePrice) {}
) internal virtual returns (uint256 amountWithdrawn, uint256 sharePrice);

///@notice Loads the share price from the yield source
///@return sharePrice The current share price.
function pricePerShare() internal virtual returns (uint256 sharePrice) {}
function pricePerShare() internal view virtual returns (uint256 sharePrice);

/// LP ///

Expand All @@ -152,10 +151,10 @@ contract Hyperdrive is MultiToken, IHyperdrive {
// Update the reserves. The bond reserves are calculated so that the
// pool is initialized with the target APR.
shareReserves = shares;
bondReserves = HyperdriveMath.calculateBondReserves(
shares,
bondReserves = HyperdriveMath.calculateInitialBondReserves(
shares,
sharePrice,
initialSharePrice,
_apr,
positionDuration,
timeStretch
Expand All @@ -164,7 +163,11 @@ contract Hyperdrive is MultiToken, IHyperdrive {
// Mint LP shares to the initializer.
// TODO - Should we index the lp share and virtual reserve to shares or to underlying?
// I think in the case where price per share < 1 there may be a problem.
_mint(AssetId._LP_ASSET_ID, msg.sender, shares);
_mint(
AssetId._LP_ASSET_ID,
msg.sender,
sharePrice.mulDown(shares).add(bondReserves)
);
}

// TODO: Add slippage protection.
Expand Down Expand Up @@ -360,6 +363,44 @@ contract Hyperdrive is MultiToken, IHyperdrive {
if (_minOutput > _proceeds) revert Errors.OutputLimit();
}

/// @notice Redeems long and short withdrawal shares.
/// @param _longWithdrawalShares The long withdrawal shares to redeem.
/// @param _shortWithdrawalShares The short withdrawal shares to redeem.
function redeemWithdrawalShares(
uint256 _longWithdrawalShares,
uint256 _shortWithdrawalShares
) external {
uint256 baseProceeds = 0;

// Perform a checkpoint.
uint256 sharePrice = pricePerShare();
_applyCheckpoint(_latestCheckpoint(), sharePrice);

// Redeem the long withdrawal shares.
uint256 proceeds = _applyWithdrawalShareRedemption(
AssetId.encodeAssetId(AssetId.AssetIdPrefix.LongWithdrawalShare, 0),
_longWithdrawalShares,
longWithdrawalSharesOutstanding,
longWithdrawalShareProceeds
);

// Redeem the short withdrawal shares.
proceeds += _applyWithdrawalShareRedemption(
AssetId.encodeAssetId(
AssetId.AssetIdPrefix.ShortWithdrawalShare,
0
),
_shortWithdrawalShares,
shortWithdrawalSharesOutstanding,
shortWithdrawalShareProceeds
);

// Withdraw the funds released by redeeming the withdrawal shares.
// TODO: Better destination support.
uint256 shareProceeds = baseProceeds.divDown(sharePrice);
withdraw(shareProceeds, msg.sender);
}

/// Long ///

/// @notice Opens a long position.
Expand Down Expand Up @@ -651,7 +692,6 @@ contract Hyperdrive is MultiToken, IHyperdrive {
if (_maturityTime <= block.timestamp) {
closeSharePrice = checkpoints[_maturityTime];
}
// Recycle a local variable
_bondAmount = _bondAmount.divDown(openSharePrice).sub(sharePayment);
uint256 shortProceeds = closeSharePrice.mulDown(_bondAmount).divDown(
sharePrice
Expand Down Expand Up @@ -703,6 +743,38 @@ contract Hyperdrive is MultiToken, IHyperdrive {
}
}

/// Getters ///

/// @notice Gets info about the pool's reserves and other state that is
/// important to evaluate potential trades.
/// @return shareReserves_ The share reserves.
/// @return bondReserves_ The bond reserves.
/// @return lpTotalSupply The total supply of LP shares.
/// @return sharePrice The share price.
/// @return longsOutstanding_ The longs that haven't been closed.
/// @return shortsOutstanding_ The shorts that haven't been closed.
function getPoolInfo()
external
view
returns (
uint256 shareReserves_,
uint256 bondReserves_,
uint256 lpTotalSupply,
uint256 sharePrice,
uint256 longsOutstanding_,
uint256 shortsOutstanding_
)
{
return (
shareReserves,
bondReserves,
totalSupply[AssetId._LP_ASSET_ID],
pricePerShare(),
longsOutstanding,
shortsOutstanding
);
}

/// Helpers ///

/// @dev Applies the trading deltas from a closed long to the reserves and
Expand Down
6 changes: 6 additions & 0 deletions contracts/libraries/AssetId.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ library AssetId {
uint256 _timestamp
) internal pure returns (uint256 id) {
// [identifier: 8 bits][timestamp: 248 bits]
if (
_timestamp >
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
) {
revert Errors.InvalidTimestamp();
}
assembly {
id := or(shl(0xf8, _prefix), _timestamp)
}
Expand Down
6 changes: 3 additions & 3 deletions contracts/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ library Errors {
/// ###############
/// ### AssetId ###
/// ###############
error AssetIDCorruption();
error InvalidTimestamp();

/// #####################
/// ### BondWrapper ###
/// #####################

error BondMatured();
error InsufficientPrice();
error AlreadyClosed();
error BondMatured();
error BondNotMatured();
error InsufficientPrice();
}
71 changes: 50 additions & 21 deletions contracts/libraries/HyperdriveMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ import { Errors } from "contracts/libraries/Errors.sol";
import { FixedPointMath } from "contracts/libraries/FixedPointMath.sol";
import { YieldSpaceMath } from "contracts/libraries/YieldSpaceMath.sol";

// FIXME: The matrix of uses of flat+curve includes cases that should never
// occur. In particular, if isBondOut && t < 1 or isBondIn && t < 1, then the
// flat part refers to base tokens and the model doesn't make sense.
//
/// @author Delve
/// @title Hyperdrive
/// @notice Math for the Hyperdrive pricing model.
Expand All @@ -34,9 +30,9 @@ library HyperdriveMath {
uint256 _positionDuration,
uint256 _timeStretch
) internal pure returns (uint256 apr) {
// NOTE: This calculation is automatically scaled in the divDown operation
// NOTE: Using divDown to convert to fixed point format.
uint256 t = _positionDuration.divDown(365 days);
uint256 tau = t.divDown(_timeStretch);
uint256 tau = t.mulDown(_timeStretch);
// ((y + s) / (mu * z)) ** -tau
uint256 spotPrice = _initialSharePrice
.mulDown(_shareReserves)
Expand All @@ -47,9 +43,39 @@ library HyperdriveMath {
FixedPointMath.ONE_18.sub(spotPrice).divDown(spotPrice.mulDown(t));
}

// TODO: There is likely a more efficient formulation for when the rate is
// based on the existing share and bond reserves.
//
/// @dev Calculates the initial bond reserves assuming that the initial LP
/// receives LP shares amounting to c * z + y.
/// @param _shareReserves The pool's share reserves.
/// @param _sharePrice The pool's share price.
/// @param _initialSharePrice The pool's initial share price.
/// @param _apr The pool's APR.
/// @param _positionDuration The amount of time until maturity in seconds.
/// @param _timeStretch The time stretch parameter.
/// @return bondReserves The bond reserves that make the pool have a
/// specified APR.
function calculateInitialBondReserves(
uint256 _shareReserves,
uint256 _sharePrice,
uint256 _initialSharePrice,
uint256 _apr,
uint256 _positionDuration,
uint256 _timeStretch
) internal pure returns (uint256 bondReserves) {
// NOTE: Using divDown to convert to fixed point format.
uint256 t = _positionDuration.divDown(365 days);
uint256 tau = t.mulDown(_timeStretch);
// mu * (1 + apr * t) ** (1 / tau) - c
uint256 rhs = _initialSharePrice
.mulDown(
FixedPointMath.ONE_18.add(_apr.mulDown(t)).pow(
FixedPointMath.ONE_18.divDown(tau)
)
)
.sub(_sharePrice);
// (z / 2) * (mu * (1 + apr * t) ** (1 / tau) - c)
return _shareReserves.divDown(2 * FixedPointMath.ONE_18).mulDown(rhs);
}

/// @dev Calculates the bond reserves that will make the pool have a
/// specified APR.
/// @param _shareReserves The pool's share reserves.
Expand All @@ -68,9 +94,9 @@ library HyperdriveMath {
uint256 _positionDuration,
uint256 _timeStretch
) internal pure returns (uint256 bondReserves) {
// NOTE: This calculation is automatically scaled in the divDown operation
// NOTE: Using divDown to convert to fixed point format.
uint256 t = _positionDuration.divDown(365 days);
uint256 tau = t.divDown(_timeStretch);
uint256 tau = t.mulDown(_timeStretch);
// (1 + apr * t) ** (1 / tau)
uint256 interestFactor = FixedPointMath.ONE_18.add(_apr.mulDown(t)).pow(
FixedPointMath.ONE_18.divDown(tau)
Expand Down Expand Up @@ -248,16 +274,19 @@ library HyperdriveMath {
// the trade was applied to the share and bond reserves.
_shareReserves = _shareReserves.add(flat);
_bondReserves = _bondReserves.sub(flat.mulDown(_sharePrice));
uint256 curveIn = YieldSpaceMath.calculateInGivenOut(
_shareReserves,
_bondReserves,
_bondReserveAdjustment,
curveOut,
FixedPointMath.ONE_18.sub(_timeStretch),
_sharePrice,
_initialSharePrice,
false
);
uint256 curveIn = 0;
if (curveOut > 0) {
curveIn = YieldSpaceMath.calculateInGivenOut(
_shareReserves,
_bondReserves,
_bondReserveAdjustment,
curveOut,
FixedPointMath.ONE_18.sub(_timeStretch),
_sharePrice,
_initialSharePrice,
false
);
}
return (flat.add(curveIn), curveOut, flat.add(curveIn));
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/libraries/YieldSpaceMath.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.18;

import "contracts/libraries/FixedPointMath.sol";
import { FixedPointMath } from "contracts/libraries/FixedPointMath.sol";

// FIXME: This doesn't compute the fee but maybe it should.
//
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"clean": "forge clean",
"lint": "yarn solhint && yarn style-check && yarn spell-check && echo \"done\"",
"prettier": "npx prettier --write .",
"test": "forge test",
"test": "forge test -vvv",
"solhint": "npx solhint -f table contracts/*.sol contracts/**/*.sol",
"spell-check": "npx cspell ./**/**/**.sol --gitignore",
"style-check": "npx prettier --check .",
Expand Down
Loading