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

Ensure that Morpho is the caller of onMorphoFlashLoan #1227

Merged
merged 5 commits into from
Jan 24, 2025
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
19 changes: 19 additions & 0 deletions contracts/src/interfaces/IHyperdriveMatchingEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,18 @@ interface IHyperdriveMatchingEngine is IMorphoFlashLoanCallback {
/// @notice Thrown when an order is already expired.
error AlreadyExpired();

/// @notice Thrown when the counterparty doesn't match the counterparty
/// signed into the order.
error InvalidCounterparty();

/// @notice Thrown when the destination for the add or remove liquidity
/// options isn't configured to this contract.
error InvalidDestination();

/// @notice Thrown when the fee recipient doesn't match the fee recipient
/// signed into the order.
error InvalidFeeRecipient();

/// @notice Thrown when orders that don't cross are matched.
error InvalidMatch();

Expand All @@ -40,6 +48,10 @@ interface IHyperdriveMatchingEngine is IMorphoFlashLoanCallback {
/// Hyperdrive instance.
error MismatchedHyperdrive();

/// @notice Thrown when the `onMorphoFlashLoan` function is called by an
/// address other than Morpho.
error SenderNotMorpho();

/// @notice Emitted when orders are cancelled.
event OrdersCancelled(address indexed trader, bytes32[] orderHashes);

Expand All @@ -62,6 +74,13 @@ interface IHyperdriveMatchingEngine is IMorphoFlashLoanCallback {
struct OrderIntent {
/// @dev The trader address that will be charged when orders are matched.
address trader;
/// @dev The counterparty of the trade. If left as zero, the validation
/// is skipped.
address counterparty;
/// @dev The fee recipient of the trade. This is the address that will
/// receive any excess trading fees on the match. If left as zero,
/// the validation is skipped.
address feeRecipient;
/// @dev The Hyperdrive address where the trade will be executed.
IHyperdrive hyperdrive;
/// @dev The amount to be used in the trade. In the case of `OpenLong`,
Expand Down
48 changes: 43 additions & 5 deletions contracts/src/matching/HyperdriveMatchingEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ contract HyperdriveMatchingEngine is
/// struct.
bytes32 public constant ORDER_INTENT_TYPEHASH =
keccak256(
"OrderIntent(address trader,address hyperdrive,uint256 amount,uint256 slippageGuard,uint256 minVaultSharePrice,Options options,uint8 orderType,uint256 expiry,bytes32 salt)"
"OrderIntent(address trader,address counterparty,address feeRecipient,address hyperdrive,uint256 amount,uint256 slippageGuard,uint256 minVaultSharePrice,Options options,uint8 orderType,uint256 expiry,bytes32 salt)"
);

/// @notice The EIP712 typehash of the `IHyperdrive.Options` struct.
Expand Down Expand Up @@ -66,9 +66,19 @@ contract HyperdriveMatchingEngine is
morpho = _morpho;
}

/// @dev Ensures that the caller is Morpho.
modifier onlyMorpho() {
if (msg.sender != address(morpho)) {
revert SenderNotMorpho();
}
_;
}

/// @notice Allows a trader to cancel a list of their orders.
/// @param _orders The orders to cancel.
function cancelOrders(OrderIntent[] calldata _orders) external {
function cancelOrders(
OrderIntent[] calldata _orders
) external nonReentrant {
// Cancel all of the orders in the batch.
bytes32[] memory orderHashes = new bytes32[](_orders.length);
for (uint256 i = 0; i < _orders.length; i++) {
Expand Down Expand Up @@ -122,7 +132,8 @@ contract HyperdriveMatchingEngine is
_longOrder,
_shortOrder,
_addLiquidityOptions,
_removeLiquidityOptions
_removeLiquidityOptions,
_feeRecipient
);

// Cancel the orders so that they can't be used again.
Expand Down Expand Up @@ -157,13 +168,16 @@ contract HyperdriveMatchingEngine is
}

/// @notice Callback called when a flash loan occurs.
/// @dev This can only be called by Morpho. This ensures that the flow goes
/// through `matchOrders`, which is required to verify that the
/// validation checks are performed.
/// @dev The callback is called only if data is not empty.
/// @param _lpAmount The amount of assets that were flash loaned.
/// @param _data Arbitrary data passed to the `flashLoan` function.
function onMorphoFlashLoan(
uint256 _lpAmount,
bytes calldata _data
) external nonReentrant {
) external onlyMorpho nonReentrant {
// Decode the execution parameters. This encodes the information
// required to execute the LP, long, and short operations.
(
Expand Down Expand Up @@ -245,6 +259,8 @@ contract HyperdriveMatchingEngine is
abi.encode(
ORDER_INTENT_TYPEHASH,
_order.trader,
_order.counterparty,
_order.feeRecipient,
_order.hyperdrive,
_order.amount,
_order.slippageGuard,
Expand Down Expand Up @@ -378,13 +394,15 @@ contract HyperdriveMatchingEngine is
/// @param _shortOrder The order intent to open a short.
/// @param _addLiquidityOptions The options used when adding liquidity.
/// @param _removeLiquidityOptions The options used when removing liquidity.
/// @param _feeRecipient The fee recipient of the match.
/// @return longOrderHash The hash of the long order.
/// @return shortOrderHash The hash of the short order.
function _validateOrders(
OrderIntent calldata _longOrder,
OrderIntent calldata _shortOrder,
IHyperdrive.Options calldata _addLiquidityOptions,
IHyperdrive.Options calldata _removeLiquidityOptions
IHyperdrive.Options calldata _removeLiquidityOptions,
address _feeRecipient
) internal view returns (bytes32 longOrderHash, bytes32 shortOrderHash) {
// Ensure that the long and short orders are the correct type.
if (
Expand All @@ -394,6 +412,26 @@ contract HyperdriveMatchingEngine is
revert InvalidOrderType();
}

// Ensure that the counterparties are compatible.
if (
(_longOrder.counterparty != address(0) &&
_longOrder.counterparty != _shortOrder.trader) ||
(_shortOrder.counterparty != address(0) &&
_shortOrder.counterparty != _longOrder.trader)
) {
revert InvalidCounterparty();
}

// Ensure that the fee recipients are compatible.
if (
(_longOrder.feeRecipient != address(0) &&
_longOrder.feeRecipient != _feeRecipient) ||
(_shortOrder.feeRecipient != address(0) &&
_shortOrder.feeRecipient != _feeRecipient)
) {
revert InvalidFeeRecipient();
}

// Ensure that neither order has expired.
if (
_longOrder.expiry <= block.timestamp ||
Expand Down
8 changes: 8 additions & 0 deletions test/integrations/matching/DirectMatchTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ contract DirectMatchTest is HyperdriveTest {
IHyperdriveMatchingEngine.OrderIntent
memory longOrder = IHyperdriveMatchingEngine.OrderIntent({
trader: LP,
counterparty: HEDGER,
feeRecipient: LP,
hyperdrive: hyperdrive,
amount: 2_440_000e6,
slippageGuard: 2_499_999e6,
Expand All @@ -255,6 +257,8 @@ contract DirectMatchTest is HyperdriveTest {
IHyperdriveMatchingEngine.OrderIntent
memory shortOrder = IHyperdriveMatchingEngine.OrderIntent({
trader: HEDGER,
counterparty: LP,
feeRecipient: LP,
hyperdrive: hyperdrive,
amount: 2_500_000e6,
slippageGuard: 65_000e6,
Expand Down Expand Up @@ -315,6 +319,8 @@ contract DirectMatchTest is HyperdriveTest {
// Create two more orders and sign them.
longOrder = IHyperdriveMatchingEngine.OrderIntent({
trader: LP,
counterparty: HEDGER,
feeRecipient: LP,
hyperdrive: hyperdrive,
amount: 2_440_000e6,
slippageGuard: 2_499_999e6,
Expand All @@ -333,6 +339,8 @@ contract DirectMatchTest is HyperdriveTest {
});
shortOrder = IHyperdriveMatchingEngine.OrderIntent({
trader: HEDGER,
counterparty: LP,
feeRecipient: LP,
hyperdrive: hyperdrive,
amount: 2_500_000e6,
slippageGuard: 65_000e6,
Expand Down
Loading
Loading