Skip to content

Commit

Permalink
Adds mint and burn functions (#1220)
Browse files Browse the repository at this point in the history
* Adds an `openPair` function

* Removes some outdated comments

* Added governance fees to the mint function

* Updated the implementation of `HyperdrivePair`

* Updated the `mint` logic and wired it up

* Added unit test cases for the `mint` function

* Adds comprehensive unit tests for the mint function

* Adds a `minOutput` parameter to `mint`

* Wrote a comprehensive integration test suite for the `mint` function

* Started implementing `burn`

* Made some targeted fixes to `mint`

* Added zombie interest to the `burn` flow and cleaned up `HyperdrivePair`

* Started adding a test suite for the `burn` function

* Added the remaining test cases. Some of them are broken.

* Fixed the remaining `burn` unit tests

* Added an integration test suite for `burn`

* Bumping solidity version of mint to match rest of repo (#1235)

* Added tests for zombie interest for `mint` and `burn`

* Added more integration tests for `mint` and `burn`

* Added a negative interest test for `mint`

* Addressed review feedback from @Sean329

* Committed incremental progress

* Added more `mint` and `burn` related cases to the `InstanceTest` suite

* Fixed the failing `test_burn_with_base` tests

* Added another instance test and got all of the tests working

* Uncommented and fixed another test

* Uncommented the remaining test

* Fixed the code size issue

* Removed fixmes -- the investigation showed that the calculations worked correctly

* Fixed some of the CI jobs

* Fixed the LPWithdrawal tests

* Attempted to fix the code coverage job

* Removed the code coverage badge

* Fixed a rare issue in the fixed point math tests

---------

Co-authored-by: Sheng Lundquist <[email protected]>
  • Loading branch information
jalextowle and slundqui authored Jan 23, 2025
1 parent 0a80019 commit 715e438
Show file tree
Hide file tree
Showing 72 changed files with 5,133 additions and 60 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
run: |
FOUNDRY_PROFILE=lite FOUNDRY_FUZZ_RUNS=100 forge coverage --report lcov
sudo apt-get install lcov
lcov --remove lcov.info -o lcov.info 'test/*' 'script/*'
lcov --remove lcov.info -o lcov.info 'test/*'
- name: Edit lcov.info
run: |
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
[![Tests](https://github.com/delvtech/hyperdrive/actions/workflows/solidity_test.yml/badge.svg)](https://github.com/delvtech/hyperdrive/actions/workflows/solidity_test.yml)
[![Coverage](https://coveralls.io/repos/github/delvtech/hyperdrive/badge.svg?branch=main&t=vnW3xG&kill_cache=1&service=github)](https://coveralls.io/github/delvtech/hyperdrive?branch=main)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/delvtech/elf-contracts/blob/master/LICENSE)
[![Static Badge](https://img.shields.io/badge/DELV-Terms%20Of%20Service-orange)](https://delv-public.s3.us-east-2.amazonaws.com/delv-terms-of-service.pdf)

Expand Down
22 changes: 22 additions & 0 deletions contracts/src/external/Hyperdrive.sol
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,28 @@ abstract contract Hyperdrive is
_delegate(target4);
}

/// Pairs ///

/// @inheritdoc IHyperdriveCore
function mint(
uint256,
uint256,
uint256,
IHyperdrive.PairOptions calldata
) external payable returns (uint256, uint256) {
_delegate(target4);
}

/// @inheritdoc IHyperdriveCore
function burn(
uint256,
uint256,
uint256,
IHyperdrive.Options calldata
) external returns (uint256) {
_delegate(target4);
}

/// Checkpoints ///

/// @inheritdoc IHyperdriveCore
Expand Down
2 changes: 2 additions & 0 deletions contracts/src/external/HyperdriveTarget0.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { HyperdriveCheckpoint } from "../internal/HyperdriveCheckpoint.sol";
import { HyperdriveLong } from "../internal/HyperdriveLong.sol";
import { HyperdriveLP } from "../internal/HyperdriveLP.sol";
import { HyperdriveMultiToken } from "../internal/HyperdriveMultiToken.sol";
import { HyperdrivePair } from "../internal/HyperdrivePair.sol";
import { HyperdriveShort } from "../internal/HyperdriveShort.sol";
import { HyperdriveStorage } from "../internal/HyperdriveStorage.sol";
import { AssetId } from "../libraries/AssetId.sol";
Expand All @@ -30,6 +31,7 @@ abstract contract HyperdriveTarget0 is
HyperdriveLP,
HyperdriveLong,
HyperdriveShort,
HyperdrivePair,
HyperdriveCheckpoint
{
using FixedPointMath for uint256;
Expand Down
2 changes: 2 additions & 0 deletions contracts/src/external/HyperdriveTarget1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { HyperdriveCheckpoint } from "../internal/HyperdriveCheckpoint.sol";
import { HyperdriveLong } from "../internal/HyperdriveLong.sol";
import { HyperdriveLP } from "../internal/HyperdriveLP.sol";
import { HyperdriveMultiToken } from "../internal/HyperdriveMultiToken.sol";
import { HyperdrivePair } from "../internal/HyperdrivePair.sol";
import { HyperdriveShort } from "../internal/HyperdriveShort.sol";
import { HyperdriveStorage } from "../internal/HyperdriveStorage.sol";

Expand All @@ -23,6 +24,7 @@ abstract contract HyperdriveTarget1 is
HyperdriveLP,
HyperdriveLong,
HyperdriveShort,
HyperdrivePair,
HyperdriveCheckpoint
{
/// @notice Instantiates target1.
Expand Down
2 changes: 2 additions & 0 deletions contracts/src/external/HyperdriveTarget2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { HyperdriveCheckpoint } from "../internal/HyperdriveCheckpoint.sol";
import { HyperdriveLong } from "../internal/HyperdriveLong.sol";
import { HyperdriveLP } from "../internal/HyperdriveLP.sol";
import { HyperdriveMultiToken } from "../internal/HyperdriveMultiToken.sol";
import { HyperdrivePair } from "../internal/HyperdrivePair.sol";
import { HyperdriveShort } from "../internal/HyperdriveShort.sol";
import { HyperdriveStorage } from "../internal/HyperdriveStorage.sol";

Expand All @@ -23,6 +24,7 @@ abstract contract HyperdriveTarget2 is
HyperdriveLP,
HyperdriveLong,
HyperdriveShort,
HyperdrivePair,
HyperdriveCheckpoint
{
/// @notice Instantiates target2.
Expand Down
2 changes: 2 additions & 0 deletions contracts/src/external/HyperdriveTarget3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { HyperdriveCheckpoint } from "../internal/HyperdriveCheckpoint.sol";
import { HyperdriveLong } from "../internal/HyperdriveLong.sol";
import { HyperdriveLP } from "../internal/HyperdriveLP.sol";
import { HyperdriveMultiToken } from "../internal/HyperdriveMultiToken.sol";
import { HyperdrivePair } from "../internal/HyperdrivePair.sol";
import { HyperdriveShort } from "../internal/HyperdriveShort.sol";
import { HyperdriveStorage } from "../internal/HyperdriveStorage.sol";

Expand All @@ -23,6 +24,7 @@ abstract contract HyperdriveTarget3 is
HyperdriveLP,
HyperdriveLong,
HyperdriveShort,
HyperdrivePair,
HyperdriveCheckpoint
{
/// @notice Instantiates target3.
Expand Down
47 changes: 47 additions & 0 deletions contracts/src/external/HyperdriveTarget4.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { HyperdriveCheckpoint } from "../internal/HyperdriveCheckpoint.sol";
import { HyperdriveLong } from "../internal/HyperdriveLong.sol";
import { HyperdriveLP } from "../internal/HyperdriveLP.sol";
import { HyperdriveMultiToken } from "../internal/HyperdriveMultiToken.sol";
import { HyperdrivePair } from "../internal/HyperdrivePair.sol";
import { HyperdriveShort } from "../internal/HyperdriveShort.sol";
import { HyperdriveStorage } from "../internal/HyperdriveStorage.sol";

Expand All @@ -23,6 +24,7 @@ abstract contract HyperdriveTarget4 is
HyperdriveLP,
HyperdriveLong,
HyperdriveShort,
HyperdrivePair,
HyperdriveCheckpoint
{
/// @notice Instantiates target4.
Expand Down Expand Up @@ -86,6 +88,51 @@ abstract contract HyperdriveTarget4 is
);
}

/// Pairs ///

/// @notice Mints a pair of long and short positions that directly match
/// each other. The amount of long and short positions that are
/// created is equal to the base value of the deposit. These
/// positions are sent to the provided destinations.
/// @param _amount The amount of capital provided to open the long. The
/// units of this quantity are either base or vault shares, depending
/// on the value of `_options.asBase`.
/// @param _minOutput The minimum number of bonds to receive.
/// @param _minVaultSharePrice The minimum vault share price at which to
/// mint the bonds. This allows traders to protect themselves from
/// opening a long in a checkpoint where negative interest has
/// accrued.
/// @param _options The pair options that configure how the trade is settled.
/// @return maturityTime The maturity time of the new long and short positions.
/// @return bondAmount The bond amount of the new long and short positoins.
function mint(
uint256 _amount,
uint256 _minOutput,
uint256 _minVaultSharePrice,
IHyperdrive.PairOptions calldata _options
) external payable returns (uint256 maturityTime, uint256 bondAmount) {
return _mint(_amount, _minOutput, _minVaultSharePrice, _options);
}

/// @dev Burns a pair of long and short positions that directly match each
/// other. The capital underlying these positions is released to the
/// trader burning the positions.
/// @param _maturityTime The maturity time of the long and short positions.
/// @param _bondAmount The amount of longs and shorts to close.
/// @param _minOutput The minimum amount of proceeds to receive.
/// @param _options The options that configure how the trade is settled.
/// @return proceeds The proceeds the user receives. The units of this
/// quantity are either base or vault shares, depending on the value
/// of `_options.asBase`.
function burn(
uint256 _maturityTime,
uint256 _bondAmount,
uint256 _minOutput,
IHyperdrive.Options calldata _options
) external returns (uint256 proceeds) {
return _burn(_maturityTime, _bondAmount, _minOutput, _options);
}

/// Checkpoints ///

/// @notice Allows anyone to mint a new checkpoint.
Expand Down
15 changes: 15 additions & 0 deletions contracts/src/interfaces/IHyperdrive.sol
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,21 @@ interface IHyperdrive is
bytes extraData;
}

struct PairOptions {
/// @dev The address that receives the long proceeds from a pair action.
address longDestination;
/// @dev The address that receives the short proceeds from a pair action.
address shortDestination;
/// @dev A boolean indicating that the trade or LP action should be
/// settled in base if true and in the yield source shares if false.
bool asBase;
/// @dev Additional data that can be used to implement custom logic in
/// implementation contracts. By convention, the last 32 bytes of
/// extra data are ignored by instances and "passed through" to the
/// event. This can be used to pass metadata through transactions.
bytes extraData;
}

/// Errors ///

/// @notice Thrown when the inputs to a batch transfer don't match in
Expand Down
41 changes: 41 additions & 0 deletions contracts/src/interfaces/IHyperdriveCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,47 @@ interface IHyperdriveCore is IMultiTokenCore {
IHyperdrive.Options calldata _options
) external returns (uint256 proceeds, uint256 withdrawalSharesRedeemed);

/// Pairs ///

/// @notice Mints a pair of long and short positions that directly match
/// each other. The amount of long and short positions that are
/// created is equal to the base value of the deposit. These
/// positions are sent to the provided destinations.
/// @param _amount The amount of capital provided to open the long. The
/// units of this quantity are either base or vault shares, depending
/// on the value of `_options.asBase`.
/// @param _minOutput The minimum number of bonds to receive.
/// @param _minVaultSharePrice The minimum vault share price at which to
/// mint the bonds. This allows traders to protect themselves from
/// opening a long in a checkpoint where negative interest has
/// accrued.
/// @param _options The pair options that configure how the trade is settled.
/// @return maturityTime The maturity time of the new long and short positions.
/// @return bondAmount The bond amount of the new long and short positoins.
function mint(
uint256 _amount,
uint256 _minOutput,
uint256 _minVaultSharePrice,
IHyperdrive.PairOptions calldata _options
) external payable returns (uint256 maturityTime, uint256 bondAmount);

/// @dev Burns a pair of long and short positions that directly match each
/// other. The capital underlying these positions is released to the
/// trader burning the positions.
/// @param _maturityTime The maturity time of the long and short positions.
/// @param _bondAmount The amount of longs and shorts to close.
/// @param _minOutput The minimum amount of proceeds to receive.
/// @param _options The options that configure how the trade is settled.
/// @return proceeds The proceeds the user receives. The units of this
/// quantity are either base or vault shares, depending on the value
/// of `_options.asBase`.
function burn(
uint256 _maturityTime,
uint256 _bondAmount,
uint256 _minOutput,
IHyperdrive.Options calldata _options
) external returns (uint256 proceeds);

/// Checkpoints ///

/// @notice Attempts to mint a checkpoint with the specified checkpoint time.
Expand Down
28 changes: 28 additions & 0 deletions contracts/src/interfaces/IHyperdriveEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,34 @@ interface IHyperdriveEvents is IMultiTokenEvents {
bytes extraData
);

/// @notice Emitted when a pair of long and short positions are minted.
event Mint(
address indexed longTrader,
address indexed shortTrader,
uint256 indexed maturityTime,
uint256 longAssetId,
uint256 shortAssetId,
uint256 amount,
uint256 vaultSharePrice,
bool asBase,
uint256 bondAmount,
bytes extraData
);

/// @notice Emitted when a pair of long and short positions are burned.
event Burn(
address indexed trader,
address indexed destination,
uint256 indexed maturityTime,
uint256 longAssetId,
uint256 shortAssetId,
uint256 amount,
uint256 vaultSharePrice,
bool asBase,
uint256 bondAmount,
bytes extraData
);

/// @notice Emitted when a checkpoint is created.
event CreateCheckpoint(
uint256 indexed checkpointTime,
Expand Down
42 changes: 30 additions & 12 deletions contracts/src/internal/HyperdriveBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,20 @@ abstract contract HyperdriveBase is IHyperdriveEvents, HyperdriveStorage {
/// @dev Process a deposit in either base or vault shares.
/// @param _amount The amount of capital to deposit. The units of this
/// quantity are either base or vault shares, depending on the value
/// of `_options.asBase`.
/// @param _options The options that configure how the deposit is
/// settled. In particular, the currency used in the deposit is
/// specified here. Aside from those options, yield sources can
/// choose to implement additional options.
/// of `_asBase`.
/// @param _asBase A flag indicating if the deposit should be made in base
/// or in vault shares.
/// @param _extraData Additional data that can be used to implement custom
/// logic in implementation contracts. By convention, the last 32
/// bytes of extra data are ignored by instances and "passed through"
/// to the event. This can be used to pass metadata through
/// transactions.
/// @return sharesMinted The shares created by this deposit.
/// @return vaultSharePrice The vault share price.
function _deposit(
uint256 _amount,
IHyperdrive.Options calldata _options
bool _asBase,
bytes calldata _extraData
) internal returns (uint256 sharesMinted, uint256 vaultSharePrice) {
// WARN: This logic doesn't account for slippage in the conversion
// from base to shares. If deposits to the yield source incur
Expand All @@ -50,19 +54,16 @@ abstract contract HyperdriveBase is IHyperdriveEvents, HyperdriveStorage {

// Deposit with either base or shares depending on the provided options.
uint256 refund;
if (_options.asBase) {
if (_asBase) {
// Process the deposit in base.
(sharesMinted, refund) = _depositWithBase(
_amount,
_options.extraData
);
(sharesMinted, refund) = _depositWithBase(_amount, _extraData);
} else {
// The refund is equal to the full message value since ETH will
// never be a shares asset.
refund = msg.value;

// Process the deposit in shares.
_depositWithShares(_amount, _options.extraData);
_depositWithShares(_amount, _extraData);
}

// Calculate the vault share price.
Expand Down Expand Up @@ -198,6 +199,23 @@ abstract contract HyperdriveBase is IHyperdriveEvents, HyperdriveStorage {
}
}

/// @dev A yield source dependent check that verifies that the provided
/// pair options are valid. The default check is that the destinations
/// are non-zero to prevent users from accidentally transferring funds
/// to the zero address. Custom integrations can override this to
/// implement additional checks.
/// @param _options The provided options for the transaction.
function _checkPairOptions(
IHyperdrive.PairOptions calldata _options
) internal pure virtual {
if (
_options.longDestination == address(0) ||
_options.shortDestination == address(0)
) {
revert IHyperdrive.RestrictedZeroAddress();
}
}

/// @dev Convert an amount of vault shares to an amount of base.
/// @param _shareAmount The vault shares amount.
/// @return baseAmount The base amount.
Expand Down
6 changes: 4 additions & 2 deletions contracts/src/internal/HyperdriveLP.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ abstract contract HyperdriveLP is
// their contribution was worth.
(uint256 shareContribution, uint256 vaultSharePrice) = _deposit(
_contribution,
_options
_options.asBase,
_options.extraData
);

// Ensure that the contribution is large enough to set aside the minimum
Expand Down Expand Up @@ -210,7 +211,8 @@ abstract contract HyperdriveLP is
// Deposit for the user, this call also transfers from them
(uint256 shareContribution, uint256 vaultSharePrice) = _deposit(
_contribution,
_options
_options.asBase,
_options.extraData
);

// Perform a checkpoint.
Expand Down
3 changes: 2 additions & 1 deletion contracts/src/internal/HyperdriveLong.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ abstract contract HyperdriveLong is IHyperdriveEvents, HyperdriveLP {
// Deposit the user's input amount.
(uint256 sharesDeposited, uint256 vaultSharePrice) = _deposit(
_amount,
_options
_options.asBase,
_options.extraData
);

// Enforce the minimum user outputs and the minimum vault share price.
Expand Down
Loading

0 comments on commit 715e438

Please sign in to comment.