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

WIP: Adds an openPair function #1220

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
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
14 changes: 14 additions & 0 deletions contracts/src/interfaces/IHyperdriveEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,20 @@ interface IHyperdriveEvents is IMultiTokenEvents {
bytes extraData
);

/// @notice Emitted when a pair of long and short positions are opened.
event OpenPair(
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 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
173 changes: 173 additions & 0 deletions contracts/src/internal/HyperdrivePair.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.22;

import { IHyperdrive } from "../interfaces/IHyperdrive.sol";
import { IHyperdriveEvents } from "../interfaces/IHyperdriveEvents.sol";
import { AssetId } from "../libraries/AssetId.sol";
import { FixedPointMath, ONE } from "../libraries/FixedPointMath.sol";
import { HyperdriveMath } from "../libraries/HyperdriveMath.sol";
import { LPMath } from "../libraries/LPMath.sol";
import { SafeCast } from "../libraries/SafeCast.sol";
import { HyperdriveBase } from "./HyperdriveLP.sol";
import { HyperdriveMultiToken } from "./HyperdriveMultiToken.sol";

/// @author DELV
/// @title HyperdriveLong
/// @notice Implements the long accounting for Hyperdrive.
/// @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.
abstract contract HyperdrivePair is
IHyperdriveEvents,
HyperdriveBase,
HyperdriveMultiToken
{
using FixedPointMath for uint256;
using FixedPointMath for int256;
using SafeCast for uint256;
using SafeCast for int256;

// FIXME: Add in a governance fee that is taken from the deposit amount and
// reduces the bond amount earned.
//
/// @dev Opens 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 _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 _openPair(
uint256 _amount,
uint256 _minVaultSharePrice,
IHyperdrive.PairOptions calldata _options
)
internal
nonReentrant
isNotPaused
returns (uint256 maturityTime, uint256 bondAmount)
{
// Check that the message value is valid.
_checkMessageValue();

// Check that the provided options are valid.
_checkPairOptions(_options);

// Deposit the user's input amount. The amount of base deposited is
// equal to the amount of bonds that will be minted.
(uint256 sharesDeposited, uint256 vaultSharePrice) = _deposit(
_amount,
_options.asBase,
_options.extraData
);
bondAmount = sharesDeposited.mulDown(vaultSharePrice);

// Enforce the minimum vault share price.
if (vaultSharePrice < _minVaultSharePrice) {
revert IHyperdrive.MinimumSharePrice();
}

// Perform a checkpoint.
uint256 latestCheckpoint = _latestCheckpoint();
_applyCheckpoint(
latestCheckpoint,
vaultSharePrice,
LPMath.SHARE_PROCEEDS_MAX_ITERATIONS,
true
);

// Apply the state changes caused by creating the pair.
maturityTime = latestCheckpoint + _positionDuration;
_applyCreatePair(maturityTime, sharesDeposited, bondAmount);

// Mint bonds equal in value to the base deposited.
uint256 longAssetId = AssetId.encodeAssetId(
AssetId.AssetIdPrefix.Long,
maturityTime
);
uint256 shortAssetId = AssetId.encodeAssetId(
AssetId.AssetIdPrefix.Short,
maturityTime
);
_mint(longAssetId, _options.longDestination, bondAmount);
_mint(shortAssetId, _options.shortDestination, bondAmount);

// Emit an OpenPair event.
emit OpenPair(
_options.longDestination,
_options.shortDestination,
maturityTime,
longAssetId,
shortAssetId,
_amount,
vaultSharePrice,
_options.asBase,
bondAmount,
_options.extraData
);

return (maturityTime, bondAmount);
}

// FIXME
// function _redeemPair(
// uint256 _amount,
// IHyperdrive.Options calldata _options
// ) internal returns (uint256 maturityTime, uint256 longAmount, uint256 shortAmount) {
// // FIXME
// }

/// @dev Applies state changes to create a pair of matched long and short
/// positions. This operation leaves the pool's solvency and idle
/// capital unchanged because the positions fully net out. Specifically:
///
/// - Share reserves, share adjustments, and bond reserves remain
/// constant since the provided capital backs the positions directly.
/// - Solvency remains constant because the net effect of matching long
/// and short positions is neutral.
/// - Idle capital is unaffected since no excess funds are added or
/// removed during this process.
///
/// Therefore:
///
/// - Solvency checks are unnecessary.
/// - Idle capital does not need to be redistributed to LPs.
/// @param _maturityTime The maturity time of the pair of long and short
/// positions
/// @param _sharesDeposited The amount of shares deposited.
/// @param _bondAmount The amount of bonds created.
function _applyCreatePair(
uint256 _maturityTime,
uint256 _sharesDeposited,
uint256 _bondAmount
) internal {
// Update the average maturity time of longs and short positions and the
// amount of long and short positions outstanding. Everything else
// remains constant.
_marketState.longAverageMaturityTime = uint256(
_marketState.longAverageMaturityTime
)
.updateWeightedAverage(
_marketState.longsOutstanding,
_maturityTime * ONE, // scale up to fixed point scale
_bondAmount,
true
)
.toUint128();
_marketState.shortAverageMaturityTime = uint256(
_marketState.shortAverageMaturityTime
)
.updateWeightedAverage(
_marketState.shortsOutstanding,
_maturityTime * ONE, // scale up to fixed point scale
_bondAmount,
true
)
.toUint128();
_marketState.longsOutstanding += _bondAmount.toUint128();
_marketState.shortsOutstanding += _bondAmount.toUint128();
}
}
2 changes: 1 addition & 1 deletion contracts/src/internal/HyperdriveShort.sol
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ abstract contract HyperdriveShort is IHyperdriveEvents, HyperdriveLP {
if (_maxDeposit < deposit) {
revert IHyperdrive.OutputLimit();
}
_deposit(deposit, _options);
_deposit(deposit, _options.asBase, _options.extraData);

// Apply the state updates caused by opening the short.
// Note: Updating the state using the result using the
Expand Down
2 changes: 1 addition & 1 deletion contracts/test/MockERC4626Hyperdrive.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ contract MockERC4626Hyperdrive is ERC4626Hyperdrive {
uint256 _amount,
IHyperdrive.Options calldata _options
) public returns (uint256 sharesMinted, uint256 vaultSharePrice) {
return _deposit(_amount, _options);
return _deposit(_amount, _options.asBase, _options.extraData);
}

function withdraw(
Expand Down
Loading