From dae1989ae1e23fa00c924654ea0b1f3ee9abaff4 Mon Sep 17 00:00:00 2001 From: Xiangyu Xu Date: Tue, 25 Feb 2025 23:11:39 +0800 Subject: [PATCH 1/7] Added more tests to Matching Engine V2 --- .../HyperdriveMatchingEngineV2Test.t.sol | 250 ++++++++++++++++++ 1 file changed, 250 insertions(+) diff --git a/test/units/matching/HyperdriveMatchingEngineV2Test.t.sol b/test/units/matching/HyperdriveMatchingEngineV2Test.t.sol index c454ec6ea..9c3593985 100644 --- a/test/units/matching/HyperdriveMatchingEngineV2Test.t.sol +++ b/test/units/matching/HyperdriveMatchingEngineV2Test.t.sol @@ -734,6 +734,256 @@ contract HyperdriveMatchingEngineV2Test is HyperdriveTest { vm.stopPrank(); } + /// @dev Tests fillOrder with partial fill + function test_fillOrder_partialFill() public { + // Create maker order with large amount + IHyperdriveMatchingEngineV2.OrderIntent + memory makerOrder = _createOrderIntent( + alice, + address(0), + 200_000e18, + 190_000e18, + IHyperdriveMatchingEngineV2.OrderType.OpenLong + ); + makerOrder.signature = _signOrderIntent(makerOrder, alicePK); + + // Create taker order with smaller amount + IHyperdriveMatchingEngineV2.OrderIntent + memory takerOrder = _createOrderIntent( + bob, + address(0), + 0, + 95_000e18, // Half of maker's amount + IHyperdriveMatchingEngineV2.OrderType.OpenShort + ); + + // Record balances and order state before + uint256 aliceLongBalanceBefore = _getLongBalance(alice); + bytes32 orderHash = matchingEngine.hashOrderIntent(makerOrder); + (uint128 bondAmountUsedBefore, ) = matchingEngine.orderAmountsUsed( + orderHash + ); + + // Fill order + vm.startPrank(bob); + matchingEngine.fillOrder(makerOrder, takerOrder); + vm.stopPrank(); + + // Verify partial fill + (uint128 bondAmountUsedAfter, ) = matchingEngine.orderAmountsUsed( + orderHash + ); + assertGt(bondAmountUsedAfter - bondAmountUsedBefore, 95_000e18); + assertEq( + _getLongBalance(alice) - aliceLongBalanceBefore, + bondAmountUsedAfter + ); + + // Verify order can still be filled further + assertFalse(matchingEngine.isCancelled(orderHash)); + } + + /// @dev Tests fillOrder with maturity time constraints + function test_fillOrder_maturityTimeConstraints() public { + uint256 currentTime = block.timestamp; + uint256 minMaturityTime = currentTime + 1 days; + uint256 maxMaturityTime = currentTime + 7 days; + + // Create maker order with maturity time constraints + IHyperdriveMatchingEngineV2.OrderIntent + memory makerOrder = _createOrderIntent( + alice, + address(0), + 100_000e18, + 95_000e18, + IHyperdriveMatchingEngineV2.OrderType.OpenLong + ); + makerOrder.minMaturityTime = minMaturityTime; + makerOrder.maxMaturityTime = maxMaturityTime; + makerOrder.signature = _signOrderIntent(makerOrder, alicePK); + + // Test taker order with invalid maturity time + IHyperdriveMatchingEngineV2.OrderIntent + memory invalidTakerOrder = _createOrderIntent( + bob, + address(0), + 0, + 95_000e18, + IHyperdriveMatchingEngineV2.OrderType.OpenShort + ); + // Set maturity time to be outside of maker's range, and actually + // it does not matter, as the taker's maturity time is not checked + // in this case. + invalidTakerOrder.minMaturityTime = maxMaturityTime + 1 days; + invalidTakerOrder.maxMaturityTime = maxMaturityTime + 2 days; + + // Will revert because the test config has position duration of 365 days. + vm.expectRevert( + IHyperdriveMatchingEngineV2.InvalidMaturityTime.selector + ); + vm.startPrank(bob); + matchingEngine.fillOrder(makerOrder, invalidTakerOrder); + vm.stopPrank(); + + // Test with valid maturity time + maxMaturityTime = currentTime + 365 days; + makerOrder.maxMaturityTime = maxMaturityTime; + makerOrder.signature = _signOrderIntent(makerOrder, alicePK); + + IHyperdriveMatchingEngineV2.OrderIntent + memory validTakerOrder = _createOrderIntent( + bob, + address(0), + 0, + 95_000e18, + IHyperdriveMatchingEngineV2.OrderType.OpenShort + ); + validTakerOrder.minMaturityTime = maxMaturityTime + 1 days; + validTakerOrder.maxMaturityTime = maxMaturityTime + 2 days; + + vm.startPrank(bob); + matchingEngine.fillOrder(makerOrder, validTakerOrder); + vm.stopPrank(); + } + + /// @dev Tests fillOrder with counterparty restriction + function test_fillOrder_counterpartyRestriction() public { + // Create maker order with specific counterparty + IHyperdriveMatchingEngineV2.OrderIntent + memory makerOrder = _createOrderIntent( + alice, + bob, // Only bob can fill + 100_000e18, + 95_000e18, + IHyperdriveMatchingEngineV2.OrderType.OpenLong + ); + makerOrder.signature = _signOrderIntent(makerOrder, alicePK); + + // Try to fill with wrong counterparty (celine) + IHyperdriveMatchingEngineV2.OrderIntent + memory invalidTakerOrder = _createOrderIntent( + celine, + address(0), + 0, + 95_000e18, + IHyperdriveMatchingEngineV2.OrderType.OpenShort + ); + + vm.expectRevert( + IHyperdriveMatchingEngineV2.InvalidCounterparty.selector + ); + vm.startPrank(celine); + matchingEngine.fillOrder(makerOrder, invalidTakerOrder); + vm.stopPrank(); + + // Fill with correct counterparty (bob) + IHyperdriveMatchingEngineV2.OrderIntent + memory validTakerOrder = _createOrderIntent( + bob, + address(0), + 0, + 95_000e18, + IHyperdriveMatchingEngineV2.OrderType.OpenShort + ); + + vm.startPrank(bob); + matchingEngine.fillOrder(makerOrder, validTakerOrder); + vm.stopPrank(); + } + + /// @dev Tests fillOrder with custom destination address + function test_fillOrder_customDestination() public { + // Create maker order with custom destination + IHyperdriveMatchingEngineV2.OrderIntent + memory makerOrder = _createOrderIntent( + alice, + address(0), + 100_000e18, + 95_000e18, + IHyperdriveMatchingEngineV2.OrderType.OpenLong + ); + makerOrder.options.destination = celine; // Positions go to celine + makerOrder.signature = _signOrderIntent(makerOrder, alicePK); + + // Create taker order with custom destination + IHyperdriveMatchingEngineV2.OrderIntent + memory takerOrder = _createOrderIntent( + bob, + address(0), + 0, + 95_000e18, + IHyperdriveMatchingEngineV2.OrderType.OpenShort + ); + takerOrder.options.destination = address(0xdead); // Positions go to 0xdead + + // Record balances before + uint256 celineLongBalanceBefore = _getLongBalance(celine); + uint256 deadShortBalanceBefore = _getShortBalance(address(0xdead)); + + // Fill order + vm.startPrank(bob); + matchingEngine.fillOrder(makerOrder, takerOrder); + vm.stopPrank(); + + // Verify positions went to correct destinations + assertGt(_getLongBalance(celine), celineLongBalanceBefore); + assertGt(_getShortBalance(address(0xdead)), deadShortBalanceBefore); + assertEq(_getLongBalance(alice), 0); + assertEq(_getShortBalance(bob), 0); + } + + /// @dev Tests fillOrder with multiple fills until completion + function test_fillOrder_multipleFilsUntilCompletion() public { + // Create large maker order + IHyperdriveMatchingEngineV2.OrderIntent + memory makerOrder = _createOrderIntent( + alice, + address(0), + 300_000e18, + 285_000e18, + IHyperdriveMatchingEngineV2.OrderType.OpenLong + ); + makerOrder.signature = _signOrderIntent(makerOrder, alicePK); + + // Fill order in three parts + for (uint256 i = 0; i < 3; i++) { + IHyperdriveMatchingEngineV2.OrderIntent + memory takerOrder = _createOrderIntent( + bob, + address(0), + 0, + 95_000e18, + IHyperdriveMatchingEngineV2.OrderType.OpenShort + ); + + vm.startPrank(bob); + matchingEngine.fillOrder(makerOrder, takerOrder); + vm.stopPrank(); + } + + // Verify order is now fully filled + bytes32 orderHash = matchingEngine.hashOrderIntent(makerOrder); + (uint128 bondAmountUsed, ) = matchingEngine.orderAmountsUsed(orderHash); + assertGt(bondAmountUsed, 285_000e18); + + // Try to fill again + IHyperdriveMatchingEngineV2.OrderIntent + memory finalTakerOrder = _createOrderIntent( + bob, + address(0), + 0, + 95_000e18, + IHyperdriveMatchingEngineV2.OrderType.OpenShort + ); + + vm.expectRevert( + IHyperdriveMatchingEngineV2.AlreadyFullyExecuted.selector + ); + vm.startPrank(bob); + matchingEngine.fillOrder(makerOrder, finalTakerOrder); + vm.stopPrank(); + } + // Helper functions. /// @dev Creates an order intent. From 58057637e59068c1e47105d9d45eda3732798cfc Mon Sep 17 00:00:00 2001 From: Xiangyu Xu Date: Thu, 27 Feb 2025 00:42:04 +0800 Subject: [PATCH 2/7] temp progress - integration testing --- .../MatchingEngineV2IntegrationTest.t.sol | 281 ++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol diff --git a/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol b/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol new file mode 100644 index 000000000..8373d033e --- /dev/null +++ b/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +import { IERC20 } from "../../../contracts/src/interfaces/IERC20.sol"; +import { IHyperdrive } from "../../../contracts/src/interfaces/IHyperdrive.sol"; +import { IHyperdriveMatchingEngineV2 } from "../../../contracts/src/interfaces/IHyperdriveMatchingEngineV2.sol"; +import { AssetId } from "../../../contracts/src/libraries/AssetId.sol"; +import { FixedPointMath } from "../../../contracts/src/libraries/FixedPointMath.sol"; +import { HyperdriveMatchingEngineV2 } from "../../../contracts/src/matching/HyperdriveMatchingEngineV2.sol"; +import { HyperdriveTest } from "../../utils/HyperdriveTest.sol"; +import { HyperdriveUtils } from "../../utils/HyperdriveUtils.sol"; +import { Lib } from "../../utils/Lib.sol"; + +/// @dev This test suite tests if TOKEN_AMOUNT_BUFFER in HyperdriveMatchingEngineV2 +/// is sufficient for successful minting operations across different pools. +contract TokenBufferTest is HyperdriveTest { + using FixedPointMath for *; + using HyperdriveUtils for *; + using Lib for *; + + /// @dev The USDe/DAI Hyperdrive pool on mainnet. + address internal constant USDE_DAI_POOL = + 0xA29A771683b4857bBd16e1e4f27D5B6bfF53209B; + + /// @dev The wstETH/USDC Hyperdrive pool on mainnet. + address internal constant WSTETH_USDC_POOL = + 0xc8D47DE20F7053Cc02504600596A647A482Bbc46; + + /// @dev The DAI token address. + address internal constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + + /// @dev The USDe token address. + address internal constant USDE = 0x4c9EDD5852cd905f086C759E8383e09bff1E68B3; + + /// @dev The USDC token address. + address internal constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + + /// @dev The wstETH token address. + address internal constant WSTETH = + 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; + + /// @dev Charlie will be the surplus recipient. + address internal constant CHARLIE = + 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC; + + /// @dev The Hyperdrive matching engine that is deployed. + IHyperdriveMatchingEngineV2 internal matchingEngine; + + /// @dev The USDe/DAI Hyperdrive instance. + IHyperdrive internal usdeDaiHyperdrive; + + /// @dev The wstETH/USDC Hyperdrive instance. + IHyperdrive internal wstethUsdcHyperdrive; + + /// @dev Counter for successful trades. + uint256 public successfulTrades; + + /// @dev Counter for failed trades. + uint256 public failedTrades; + + /// @dev Sets up the test harness on a mainnet fork. + function setUp() public override __mainnet_fork(21_931_000) { + // Run the higher-level setup logic. + super.setUp(); + + // Deploy the Hyperdrive matching engine. + matchingEngine = IHyperdriveMatchingEngineV2( + address( + new HyperdriveMatchingEngineV2("Hyperdrive Matching Engine V2") + ) + ); + + // Set up the Hyperdrive instances. + usdeDaiHyperdrive = IHyperdrive(USDE_DAI_POOL); + wstethUsdcHyperdrive = IHyperdrive(WSTETH_USDC_POOL); + + // Fund Alice and Bob with tokens. + deal(DAI, alice, 10_000_000e18); + deal(USDE, alice, 10_000_000e18); + deal(USDC, alice, 10_000_000e6); + deal(WSTETH, alice, 1000e18); + + deal(DAI, bob, 10_000_000e18); + deal(USDE, bob, 10_000_000e18); + deal(USDC, bob, 10_000_000e6); + deal(WSTETH, bob, 1000e18); + + // Approve tokens for Alice and Bob. + _approveTokens(alice, address(usdeDaiHyperdrive), IERC20(DAI)); + _approveTokens(alice, address(usdeDaiHyperdrive), IERC20(USDE)); + _approveTokens(alice, address(wstethUsdcHyperdrive), IERC20(USDC)); + _approveTokens(alice, address(wstethUsdcHyperdrive), IERC20(WSTETH)); + _approveTokens(alice, address(matchingEngine), IERC20(DAI)); + _approveTokens(alice, address(matchingEngine), IERC20(USDE)); + _approveTokens(alice, address(matchingEngine), IERC20(USDC)); + _approveTokens(alice, address(matchingEngine), IERC20(WSTETH)); + + _approveTokens(bob, address(usdeDaiHyperdrive), IERC20(DAI)); + _approveTokens(bob, address(usdeDaiHyperdrive), IERC20(USDE)); + _approveTokens(bob, address(wstethUsdcHyperdrive), IERC20(USDC)); + _approveTokens(bob, address(wstethUsdcHyperdrive), IERC20(WSTETH)); + _approveTokens(bob, address(matchingEngine), IERC20(DAI)); + _approveTokens(bob, address(matchingEngine), IERC20(USDE)); + _approveTokens(bob, address(matchingEngine), IERC20(USDC)); + _approveTokens(bob, address(matchingEngine), IERC20(WSTETH)); + } + + /// @dev Tests if TOKEN_AMOUNT_BUFFER is sufficient for USDe/DAI pool. + /// This test uses fixed time and iterates through increasing bond amounts. + function test_tokenBuffer_USDe_DAI() external { + // Use a fixed time elapsed. + uint256 timeElapsed = 1 days; + + // Advance time to simulate real-world conditions. + vm.warp(block.timestamp + timeElapsed); + + // Initialize counters for this test. + uint256 localSuccessfulTrades = 0; + uint256 localFailedTrades = 0; + + // Start with 2000e18 and increment by 50000e18 each time, for 20 iterations. + for (uint256 i = 0; i < 20; i++) { + uint256 bondAmount = 2000e18 + (i * 50000e18); + + // Calculate fund amounts and make it more than sufficient. + uint256 totalFunds = 2 * bondAmount; + + // Split funds between Alice and Bob. + uint256 aliceFundAmount = totalFunds / 2; + uint256 bobFundAmount = totalFunds - aliceFundAmount; + + // Create orders. + IHyperdriveMatchingEngineV2.OrderIntent + memory longOrder = _createOrderIntent( + alice, + bob, + usdeDaiHyperdrive, + aliceFundAmount, + bondAmount, + true, // asBase + IHyperdriveMatchingEngineV2.OrderType.OpenLong + ); + + IHyperdriveMatchingEngineV2.OrderIntent + memory shortOrder = _createOrderIntent( + bob, + alice, + usdeDaiHyperdrive, + bobFundAmount, + bondAmount, + true, // asBase + IHyperdriveMatchingEngineV2.OrderType.OpenShort + ); + + // Sign orders. + longOrder.signature = _signOrderIntent(longOrder, alicePK); + shortOrder.signature = _signOrderIntent(shortOrder, bobPK); + + // Try to match orders and record success/failure. + try matchingEngine.matchOrders(longOrder, shortOrder, CHARLIE) { + localSuccessfulTrades++; + + // Log successful trade. + emit log_named_uint( + "Successful trade with bond amount", + bondAmount + ); + + // Verify positions were created. + uint256 aliceLongBalance = usdeDaiHyperdrive.balanceOf( + AssetId.encodeAssetId( + AssetId.AssetIdPrefix.Long, + usdeDaiHyperdrive.latestCheckpoint() + + usdeDaiHyperdrive.getPoolConfig().positionDuration + ), + alice + ); + + uint256 bobShortBalance = usdeDaiHyperdrive.balanceOf( + AssetId.encodeAssetId( + AssetId.AssetIdPrefix.Short, + usdeDaiHyperdrive.latestCheckpoint() + + usdeDaiHyperdrive.getPoolConfig().positionDuration + ), + bob + ); + + assertGt( + aliceLongBalance, + 0, + "Alice should have long position" + ); + assertGt(bobShortBalance, 0, "Bob should have short position"); + } catch { + localFailedTrades++; + + // Log failed trade. + emit log_named_uint( + "Failed trade with bond amount", + bondAmount + ); + } + + // Advance block to avoid nonce issues. + vm.roll(block.number + 1); + } + + // Log summary. + emit log_named_uint( + "USDe/DAI - Total successful trades", + localSuccessfulTrades + ); + emit log_named_uint( + "USDe/DAI - Total failed trades", + localFailedTrades + ); + } + + /// @dev Helper function to create an order intent. + function _createOrderIntent( + address _trader, + address _counterparty, + IHyperdrive _hyperdrive, + uint256 _fundAmount, + uint256 _bondAmount, + bool _asBase, + IHyperdriveMatchingEngineV2.OrderType _orderType + ) internal view returns (IHyperdriveMatchingEngineV2.OrderIntent memory) { + return + IHyperdriveMatchingEngineV2.OrderIntent({ + trader: _trader, + counterparty: _counterparty, + hyperdrive: _hyperdrive, + fundAmount: _fundAmount, + bondAmount: _bondAmount, + minVaultSharePrice: 0, + options: IHyperdrive.Options({ + asBase: _asBase, + destination: _trader, + extraData: "" + }), + orderType: _orderType, + minMaturityTime: 0, + maxMaturityTime: type(uint256).max, + expiry: block.timestamp + 1 hours, + salt: bytes32( + uint256( + keccak256( + abi.encodePacked( + _trader, + _orderType, + block.timestamp + ) + ) + ) + ), + signature: new bytes(0) + }); + } + + /// @dev Helper function to sign an order intent. + function _signOrderIntent( + IHyperdriveMatchingEngineV2.OrderIntent memory _order, + uint256 _privateKey + ) internal view returns (bytes memory) { + bytes32 orderHash = matchingEngine.hashOrderIntent(_order); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(_privateKey, orderHash); + return abi.encodePacked(r, s, v); + } + + /// @dev Helper function to approve tokens. + function _approveTokens( + address _owner, + address _spender, + IERC20 _token + ) internal { + vm.startPrank(_owner); + _token.approve(_spender, type(uint256).max); + vm.stopPrank(); + } +} From 8f89268282513b9c3c491147ec04681eee58a12d Mon Sep 17 00:00:00 2001 From: Xiangyu Xu Date: Sun, 2 Mar 2025 03:29:49 -0800 Subject: [PATCH 3/7] Finished integration tests with Morpho pools --- .../MatchingEngineV2IntegrationTest.t.sol | 428 ++++++++++++++++-- 1 file changed, 379 insertions(+), 49 deletions(-) diff --git a/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol b/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol index 8373d033e..1b9f35671 100644 --- a/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol +++ b/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol @@ -1,11 +1,21 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.24; +import { IMorpho, Market, MarketParams, Id } from "morpho-blue/src/interfaces/IMorpho.sol"; +import { MarketParamsLib } from "morpho-blue/src/libraries/MarketParamsLib.sol"; +import { MorphoBlueConversions } from "../../../contracts/src/instances/morpho-blue/MorphoBlueConversions.sol"; +import { MorphoBlueHyperdrive } from "../../../contracts/src/instances/morpho-blue/MorphoBlueHyperdrive.sol"; +import { MorphoBlueTarget0 } from "../../../contracts/src/instances/morpho-blue/MorphoBlueTarget0.sol"; +import { MorphoBlueTarget1 } from "../../../contracts/src/instances/morpho-blue/MorphoBlueTarget1.sol"; +import { MorphoBlueTarget2 } from "../../../contracts/src/instances/morpho-blue/MorphoBlueTarget2.sol"; +import { MorphoBlueTarget3 } from "../../../contracts/src/instances/morpho-blue/MorphoBlueTarget3.sol"; +import { MorphoBlueTarget4 } from "../../../contracts/src/instances/morpho-blue/MorphoBlueTarget4.sol"; import { IERC20 } from "../../../contracts/src/interfaces/IERC20.sol"; import { IHyperdrive } from "../../../contracts/src/interfaces/IHyperdrive.sol"; import { IHyperdriveMatchingEngineV2 } from "../../../contracts/src/interfaces/IHyperdriveMatchingEngineV2.sol"; +import { IMorphoBlueHyperdrive } from "../../../contracts/src/interfaces/IMorphoBlueHyperdrive.sol"; import { AssetId } from "../../../contracts/src/libraries/AssetId.sol"; -import { FixedPointMath } from "../../../contracts/src/libraries/FixedPointMath.sol"; +import { FixedPointMath, ONE } from "../../../contracts/src/libraries/FixedPointMath.sol"; import { HyperdriveMatchingEngineV2 } from "../../../contracts/src/matching/HyperdriveMatchingEngineV2.sol"; import { HyperdriveTest } from "../../utils/HyperdriveTest.sol"; import { HyperdriveUtils } from "../../utils/HyperdriveUtils.sol"; @@ -17,64 +27,224 @@ contract TokenBufferTest is HyperdriveTest { using FixedPointMath for *; using HyperdriveUtils for *; using Lib for *; + using MarketParamsLib for MarketParams; - /// @dev The USDe/DAI Hyperdrive pool on mainnet. - address internal constant USDE_DAI_POOL = - 0xA29A771683b4857bBd16e1e4f27D5B6bfF53209B; + /// @dev The Morpho Blue address + IMorpho internal constant MORPHO = + IMorpho(0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb); - /// @dev The wstETH/USDC Hyperdrive pool on mainnet. - address internal constant WSTETH_USDC_POOL = - 0xc8D47DE20F7053Cc02504600596A647A482Bbc46; - - /// @dev The DAI token address. + /// @dev The DAI token address address internal constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; - /// @dev The USDe token address. + /// @dev The USDe token address address internal constant USDE = 0x4c9EDD5852cd905f086C759E8383e09bff1E68B3; - /// @dev The USDC token address. + /// @dev The USDC token address address internal constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; - /// @dev The wstETH token address. + /// @dev The wstETH token address address internal constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; - /// @dev Charlie will be the surplus recipient. + /// @dev The oracle for wstETH/USDC pool + address internal constant WSTETH_ORACLE = + 0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2; + + /// @dev The oracle for USDe/DAI pool + address internal constant USDE_ORACLE = + 0xaE4750d0813B5E37A51f7629beedd72AF1f9cA35; + + /// @dev The interest rate model for Morpho pools + address internal constant IRM = 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC; + + /// @dev The liquidation loan to value for Morpho pools + uint256 internal constant LLTV = 860000000000000000; + + /// @dev Charlie will be the surplus recipient address internal constant CHARLIE = 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC; - /// @dev The Hyperdrive matching engine that is deployed. + /// @dev The initializer of the pools + address internal constant INITIALIZER = + 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; + + /// @dev The Hyperdrive matching engine that is deployed IHyperdriveMatchingEngineV2 internal matchingEngine; - /// @dev The USDe/DAI Hyperdrive instance. + /// @dev The USDe/DAI Hyperdrive instance IHyperdrive internal usdeDaiHyperdrive; - /// @dev The wstETH/USDC Hyperdrive instance. + /// @dev The wstETH/USDC Hyperdrive instance IHyperdrive internal wstethUsdcHyperdrive; - /// @dev Counter for successful trades. - uint256 public successfulTrades; - - /// @dev Counter for failed trades. - uint256 public failedTrades; - - /// @dev Sets up the test harness on a mainnet fork. + /// @dev Sets up the test harness on a mainnet fork function setUp() public override __mainnet_fork(21_931_000) { - // Run the higher-level setup logic. + // Run the higher-level setup logic super.setUp(); - // Deploy the Hyperdrive matching engine. + // Deploy the Hyperdrive matching engine matchingEngine = IHyperdriveMatchingEngineV2( address( new HyperdriveMatchingEngineV2("Hyperdrive Matching Engine V2") ) ); - // Set up the Hyperdrive instances. - usdeDaiHyperdrive = IHyperdrive(USDE_DAI_POOL); - wstethUsdcHyperdrive = IHyperdrive(WSTETH_USDC_POOL); + // Deploy a Morpho Blue wstETH/USDC pool + IMorphoBlueHyperdrive.MorphoBlueParams + memory wstethParams = IMorphoBlueHyperdrive.MorphoBlueParams({ + morpho: MORPHO, + collateralToken: WSTETH, + oracle: WSTETH_ORACLE, + irm: IRM, + lltv: LLTV + }); + + IHyperdrive.PoolConfig memory wstethConfig = testConfig( + 0.04e18, + 182 days + ); + wstethConfig.baseToken = IERC20(USDC); + wstethConfig.vaultSharesToken = IERC20(address(0)); + wstethConfig.fees.curve = 0.01e18; + wstethConfig.fees.flat = 0.0005e18; + wstethConfig.fees.governanceLP = 0.15e18; + wstethConfig.minimumShareReserves = 1e6; + wstethConfig.minimumTransactionAmount = 1e6; + wstethConfig.initialVaultSharePrice = MorphoBlueConversions + .convertToBase( + MORPHO, + wstethConfig.baseToken, + wstethParams.collateralToken, + wstethParams.oracle, + wstethParams.irm, + wstethParams.lltv, + ONE + ); + + wstethUsdcHyperdrive = IHyperdrive( + address( + new MorphoBlueHyperdrive( + "MorphoBlueHyperdrive-wstETH-USDC", + wstethConfig, + adminController, + address( + new MorphoBlueTarget0( + wstethConfig, + adminController, + wstethParams + ) + ), + address( + new MorphoBlueTarget1( + wstethConfig, + adminController, + wstethParams + ) + ), + address( + new MorphoBlueTarget2( + wstethConfig, + adminController, + wstethParams + ) + ), + address( + new MorphoBlueTarget3( + wstethConfig, + adminController, + wstethParams + ) + ), + address( + new MorphoBlueTarget4( + wstethConfig, + adminController, + wstethParams + ) + ), + wstethParams + ) + ) + ); + + // Deploy a Morpho Blue USDe/DAI pool + IMorphoBlueHyperdrive.MorphoBlueParams + memory usdeParams = IMorphoBlueHyperdrive.MorphoBlueParams({ + morpho: MORPHO, + collateralToken: USDE, + oracle: USDE_ORACLE, + irm: IRM, + lltv: LLTV + }); + + IHyperdrive.PoolConfig memory usdeConfig = testConfig( + 0.04e18, + 182 days + ); + usdeConfig.baseToken = IERC20(DAI); + usdeConfig.vaultSharesToken = IERC20(address(0)); + usdeConfig.fees.curve = 0.01e18; + usdeConfig.fees.flat = 0.0005e18; + usdeConfig.fees.governanceLP = 0.15e18; + usdeConfig.minimumShareReserves = 1e18; + usdeConfig.minimumTransactionAmount = 1e18; + usdeConfig.initialVaultSharePrice = MorphoBlueConversions.convertToBase( + MORPHO, + usdeConfig.baseToken, + usdeParams.collateralToken, + usdeParams.oracle, + usdeParams.irm, + usdeParams.lltv, + ONE + ); + + usdeDaiHyperdrive = IHyperdrive( + address( + new MorphoBlueHyperdrive( + "MorphoBlueHyperdrive-USDe-DAI", + usdeConfig, + adminController, + address( + new MorphoBlueTarget0( + usdeConfig, + adminController, + usdeParams + ) + ), + address( + new MorphoBlueTarget1( + usdeConfig, + adminController, + usdeParams + ) + ), + address( + new MorphoBlueTarget2( + usdeConfig, + adminController, + usdeParams + ) + ), + address( + new MorphoBlueTarget3( + usdeConfig, + adminController, + usdeParams + ) + ), + address( + new MorphoBlueTarget4( + usdeConfig, + adminController, + usdeParams + ) + ), + usdeParams + ) + ) + ); - // Fund Alice and Bob with tokens. + // Fund accounts with tokens deal(DAI, alice, 10_000_000e18); deal(USDE, alice, 10_000_000e18); deal(USDC, alice, 10_000_000e6); @@ -85,7 +255,12 @@ contract TokenBufferTest is HyperdriveTest { deal(USDC, bob, 10_000_000e6); deal(WSTETH, bob, 1000e18); - // Approve tokens for Alice and Bob. + deal(DAI, INITIALIZER, 10_000_000e18); + deal(USDE, INITIALIZER, 10_000_000e18); + deal(USDC, INITIALIZER, 10_000_000e6); + deal(WSTETH, INITIALIZER, 1000e18); + + // Approve tokens _approveTokens(alice, address(usdeDaiHyperdrive), IERC20(DAI)); _approveTokens(alice, address(usdeDaiHyperdrive), IERC20(USDE)); _approveTokens(alice, address(wstethUsdcHyperdrive), IERC20(USDC)); @@ -103,33 +278,73 @@ contract TokenBufferTest is HyperdriveTest { _approveTokens(bob, address(matchingEngine), IERC20(USDE)); _approveTokens(bob, address(matchingEngine), IERC20(USDC)); _approveTokens(bob, address(matchingEngine), IERC20(WSTETH)); + + _approveTokens(INITIALIZER, address(usdeDaiHyperdrive), IERC20(DAI)); + _approveTokens(INITIALIZER, address(usdeDaiHyperdrive), IERC20(USDE)); + _approveTokens( + INITIALIZER, + address(wstethUsdcHyperdrive), + IERC20(USDC) + ); + _approveTokens( + INITIALIZER, + address(wstethUsdcHyperdrive), + IERC20(WSTETH) + ); + + // Initialize the Hyperdrive pools + vm.startPrank(INITIALIZER); + + // Initialize wstETH/USDC pool + wstethUsdcHyperdrive.initialize( + 1_000_000e6, // 1M USDC + 0.0361e18, + IHyperdrive.Options({ + asBase: true, + destination: INITIALIZER, + extraData: "" + }) + ); + + // Initialize USDe/DAI pool + usdeDaiHyperdrive.initialize( + 1_000_000e18, // 1M DAI + 0.0361e18, + IHyperdrive.Options({ + asBase: true, + destination: INITIALIZER, + extraData: "" + }) + ); + + vm.stopPrank(); } /// @dev Tests if TOKEN_AMOUNT_BUFFER is sufficient for USDe/DAI pool. /// This test uses fixed time and iterates through increasing bond amounts. function test_tokenBuffer_USDe_DAI() external { - // Use a fixed time elapsed. + // Use a fixed time elapsed uint256 timeElapsed = 1 days; - // Advance time to simulate real-world conditions. + // Advance time to simulate real-world conditions vm.warp(block.timestamp + timeElapsed); - // Initialize counters for this test. + // Initialize counters for this test uint256 localSuccessfulTrades = 0; uint256 localFailedTrades = 0; - // Start with 2000e18 and increment by 50000e18 each time, for 20 iterations. - for (uint256 i = 0; i < 20; i++) { - uint256 bondAmount = 2000e18 + (i * 50000e18); + // Start with 20000e18 and increment by 50000e18 each time, for 10 iterations + for (uint256 i = 0; i < 10; i++) { + uint256 bondAmount = 20000e18 + (i * 50000e18); - // Calculate fund amounts and make it more than sufficient. + // Calculate fund amounts and double it to make it more than sufficient uint256 totalFunds = 2 * bondAmount; - // Split funds between Alice and Bob. + // Split funds between Alice and Bob uint256 aliceFundAmount = totalFunds / 2; uint256 bobFundAmount = totalFunds - aliceFundAmount; - // Create orders. + // Create orders IHyperdriveMatchingEngineV2.OrderIntent memory longOrder = _createOrderIntent( alice, @@ -152,21 +367,21 @@ contract TokenBufferTest is HyperdriveTest { IHyperdriveMatchingEngineV2.OrderType.OpenShort ); - // Sign orders. + // Sign orders longOrder.signature = _signOrderIntent(longOrder, alicePK); shortOrder.signature = _signOrderIntent(shortOrder, bobPK); - // Try to match orders and record success/failure. + // Try to match orders and record success/failure try matchingEngine.matchOrders(longOrder, shortOrder, CHARLIE) { localSuccessfulTrades++; - // Log successful trade. + // Log successful trade emit log_named_uint( "Successful trade with bond amount", bondAmount ); - // Verify positions were created. + // Verify positions were created uint256 aliceLongBalance = usdeDaiHyperdrive.balanceOf( AssetId.encodeAssetId( AssetId.AssetIdPrefix.Long, @@ -194,18 +409,18 @@ contract TokenBufferTest is HyperdriveTest { } catch { localFailedTrades++; - // Log failed trade. + // Log failed trade emit log_named_uint( "Failed trade with bond amount", bondAmount ); } - // Advance block to avoid nonce issues. + // Advance block to avoid nonce issues vm.roll(block.number + 1); } - // Log summary. + // Log summary emit log_named_uint( "USDe/DAI - Total successful trades", localSuccessfulTrades @@ -216,7 +431,122 @@ contract TokenBufferTest is HyperdriveTest { ); } - /// @dev Helper function to create an order intent. + /// @dev Tests if TOKEN_AMOUNT_BUFFER is sufficient for wstETH/USDC pool. + /// This test uses fixed time and iterates through increasing bond amounts. + function test_tokenBuffer_wstETH_USDC() external { + // Use a fixed time elapsed + uint256 timeElapsed = 1 days; + + // Advance time to simulate real-world conditions + vm.warp(block.timestamp + timeElapsed); + + // Initialize counters for this test + uint256 localSuccessfulTrades = 0; + uint256 localFailedTrades = 0; + + // Start with 2000e6 and increment by 5000e6 each time, for 10 iterations + for (uint256 i = 0; i < 10; i++) { + uint256 bondAmount = 2000e6 + (i * 5000e6); + + // Calculate fund amounts and make it more than sufficient + uint256 totalFunds = 2 * bondAmount; + + // Split funds between Alice and Bob + uint256 aliceFundAmount = totalFunds / 2; + uint256 bobFundAmount = totalFunds - aliceFundAmount; + + // Create orders + IHyperdriveMatchingEngineV2.OrderIntent + memory longOrder = _createOrderIntent( + alice, + bob, + wstethUsdcHyperdrive, + aliceFundAmount, + bondAmount, + true, // asBase + IHyperdriveMatchingEngineV2.OrderType.OpenLong + ); + + IHyperdriveMatchingEngineV2.OrderIntent + memory shortOrder = _createOrderIntent( + bob, + alice, + wstethUsdcHyperdrive, + bobFundAmount, + bondAmount, + true, // asBase + IHyperdriveMatchingEngineV2.OrderType.OpenShort + ); + + // Sign orders + longOrder.signature = _signOrderIntent(longOrder, alicePK); + shortOrder.signature = _signOrderIntent(shortOrder, bobPK); + + // Try to match orders and record success/failure + try matchingEngine.matchOrders(longOrder, shortOrder, CHARLIE) { + localSuccessfulTrades++; + + // Log successful trade + emit log_named_uint( + "Successful trade with bond amount", + bondAmount + ); + + // Verify positions were created + uint256 aliceLongBalance = wstethUsdcHyperdrive.balanceOf( + AssetId.encodeAssetId( + AssetId.AssetIdPrefix.Long, + wstethUsdcHyperdrive.latestCheckpoint() + + wstethUsdcHyperdrive + .getPoolConfig() + .positionDuration + ), + alice + ); + + uint256 bobShortBalance = wstethUsdcHyperdrive.balanceOf( + AssetId.encodeAssetId( + AssetId.AssetIdPrefix.Short, + wstethUsdcHyperdrive.latestCheckpoint() + + wstethUsdcHyperdrive + .getPoolConfig() + .positionDuration + ), + bob + ); + + assertGt( + aliceLongBalance, + 0, + "Alice should have long position" + ); + assertGt(bobShortBalance, 0, "Bob should have short position"); + } catch { + localFailedTrades++; + + // Log failed trade + emit log_named_uint( + "Failed trade with bond amount", + bondAmount + ); + } + + // Advance block to avoid nonce issues + vm.roll(block.number + 1); + } + + // Log summary + emit log_named_uint( + "wstETH/USDC - Total successful trades", + localSuccessfulTrades + ); + emit log_named_uint( + "wstETH/USDC - Total failed trades", + localFailedTrades + ); + } + + /// @dev Helper function to create an order intent function _createOrderIntent( address _trader, address _counterparty, @@ -258,7 +588,7 @@ contract TokenBufferTest is HyperdriveTest { }); } - /// @dev Helper function to sign an order intent. + /// @dev Helper function to sign an order intent function _signOrderIntent( IHyperdriveMatchingEngineV2.OrderIntent memory _order, uint256 _privateKey @@ -268,7 +598,7 @@ contract TokenBufferTest is HyperdriveTest { return abi.encodePacked(r, s, v); } - /// @dev Helper function to approve tokens. + /// @dev Helper function to approve tokens function _approveTokens( address _owner, address _spender, From e3f4822ee9396d44de03ea1fe22a59d9545d2fd1 Mon Sep 17 00:00:00 2001 From: Xiangyu Xu Date: Tue, 4 Mar 2025 11:08:55 -0800 Subject: [PATCH 4/7] Bug fix: mint cost when asBase is false ; Fixed integration tests --- .../matching/HyperdriveMatchingEngineV2.sol | 21 +++++++++++++++---- .../MatchingEngineV2IntegrationTest.t.sol | 8 +++---- .../HyperdriveMatchingEngineV2Test.t.sol | 18 ++++++++++++---- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/contracts/src/matching/HyperdriveMatchingEngineV2.sol b/contracts/src/matching/HyperdriveMatchingEngineV2.sol index f6859e8e2..a41dfa856 100644 --- a/contracts/src/matching/HyperdriveMatchingEngineV2.sol +++ b/contracts/src/matching/HyperdriveMatchingEngineV2.sol @@ -153,7 +153,8 @@ contract HyperdriveMatchingEngineV2 is // Calculate costs and parameters. (uint256 maturityTime, uint256 cost) = _calculateMintCost( hyperdrive, - bondMatchAmount + bondMatchAmount, + _order1.options.asBase ); // Check if the maturity time is within the range. @@ -442,7 +443,8 @@ contract HyperdriveMatchingEngineV2 is // Calculate costs and parameters. (uint256 maturityTime, uint256 cost) = _calculateMintCost( hyperdrive, - bondMatchAmount + bondMatchAmount, + _makerOrder.options.asBase ); // Check if the maturity time is within the range. @@ -552,7 +554,8 @@ contract HyperdriveMatchingEngineV2 is // Calculate costs and parameters. (uint256 maturityTime, uint256 cost) = _calculateMintCost( hyperdrive, - bondMatchAmount + bondMatchAmount, + _makerOrder.options.asBase ); // Check if the maturity time is within the range. @@ -1415,11 +1418,13 @@ contract HyperdriveMatchingEngineV2 is /// @dev Calculates the cost and parameters for minting positions. /// @param _hyperdrive The Hyperdrive contract instance. /// @param _bondMatchAmount The amount of bonds to mint. + /// @param _asBase Whether the cost is in terms of base. /// @return maturityTime The maturity time for new positions. /// @return cost The total cost including fees. function _calculateMintCost( IHyperdrive _hyperdrive, - uint256 _bondMatchAmount + uint256 _bondMatchAmount, + bool _asBase ) internal view returns (uint256 maturityTime, uint256 cost) { // Get pool configuration. IHyperdrive.PoolConfig memory config = _hyperdrive.getPoolConfig(); @@ -1453,6 +1458,14 @@ contract HyperdriveMatchingEngineV2 is // NOTE: Round the governance fee calculation down to match other flows. uint256 governanceFee = 2 * flatFee.mulDown(config.fees.governanceLP); cost += governanceFee; + + if (_asBase) { + // NOTE: Round up to overestimate the cost. + cost = _hyperdrive.convertToBase(cost.divUp(vaultSharePrice)); + } else { + // NOTE: Round up to overestimate the cost. + cost = cost.divUp(vaultSharePrice); + } } /// @dev Updates either the bond amount or fund amount used for a given order. diff --git a/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol b/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol index 1b9f35671..f5ca2ecf5 100644 --- a/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol +++ b/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol @@ -333,8 +333,8 @@ contract TokenBufferTest is HyperdriveTest { uint256 localSuccessfulTrades = 0; uint256 localFailedTrades = 0; - // Start with 20000e18 and increment by 50000e18 each time, for 10 iterations - for (uint256 i = 0; i < 10; i++) { + // Start with 20000e18 and increment by 50000e18 each time, for 20 iterations + for (uint256 i = 0; i < 20; i++) { uint256 bondAmount = 20000e18 + (i * 50000e18); // Calculate fund amounts and double it to make it more than sufficient @@ -444,8 +444,8 @@ contract TokenBufferTest is HyperdriveTest { uint256 localSuccessfulTrades = 0; uint256 localFailedTrades = 0; - // Start with 2000e6 and increment by 5000e6 each time, for 10 iterations - for (uint256 i = 0; i < 10; i++) { + // Start with 2000e6 and increment by 5000e6 each time, for 20 iterations + for (uint256 i = 0; i < 20; i++) { uint256 bondAmount = 2000e6 + (i * 5000e6); // Calculate fund amounts and make it more than sufficient diff --git a/test/units/matching/HyperdriveMatchingEngineV2Test.t.sol b/test/units/matching/HyperdriveMatchingEngineV2Test.t.sol index 9c3593985..ac0cab25d 100644 --- a/test/units/matching/HyperdriveMatchingEngineV2Test.t.sol +++ b/test/units/matching/HyperdriveMatchingEngineV2Test.t.sol @@ -576,7 +576,7 @@ contract HyperdriveMatchingEngineV2Test is HyperdriveTest { function testFuzz_tokenAmountBuffer(uint256 bondAmount) public { bondAmount = bound(bondAmount, 100e18, 1_000_000e18); uint256 fundAmount1 = bondAmount / 2; - (, uint256 cost) = _calculateMintCost(bondAmount); + (, uint256 cost) = _calculateMintCost(bondAmount, true); uint256 fundAmount2 = cost + 10 - fundAmount1; // Create orders @@ -773,7 +773,7 @@ contract HyperdriveMatchingEngineV2Test is HyperdriveTest { (uint128 bondAmountUsedAfter, ) = matchingEngine.orderAmountsUsed( orderHash ); - assertGt(bondAmountUsedAfter - bondAmountUsedBefore, 95_000e18); + assertGe(bondAmountUsedAfter - bondAmountUsedBefore, 95_000e18); assertEq( _getLongBalance(alice) - aliceLongBalanceBefore, bondAmountUsedAfter @@ -964,7 +964,7 @@ contract HyperdriveMatchingEngineV2Test is HyperdriveTest { // Verify order is now fully filled bytes32 orderHash = matchingEngine.hashOrderIntent(makerOrder); (uint128 bondAmountUsed, ) = matchingEngine.orderAmountsUsed(orderHash); - assertGt(bondAmountUsed, 285_000e18); + assertGe(bondAmountUsed, 285_000e18); // Try to fill again IHyperdriveMatchingEngineV2.OrderIntent @@ -1066,10 +1066,12 @@ contract HyperdriveMatchingEngineV2Test is HyperdriveTest { /// @dev Calculates the cost and parameters for minting positions. /// @param _bondMatchAmount The amount of bonds to mint. + /// @param _asBase Whether the cost is in terms of base. /// @return maturityTime The maturity time for new positions. /// @return cost The total cost including fees. function _calculateMintCost( - uint256 _bondMatchAmount + uint256 _bondMatchAmount, + bool _asBase ) internal view returns (uint256 maturityTime, uint256 cost) { // Get pool configuration. IHyperdrive.PoolConfig memory config = hyperdrive.getPoolConfig(); @@ -1103,5 +1105,13 @@ contract HyperdriveMatchingEngineV2Test is HyperdriveTest { // NOTE: Round the governance fee calculation down to match other flows. uint256 governanceFee = 2 * flatFee.mulDown(config.fees.governanceLP); cost += governanceFee; + + if (_asBase) { + // NOTE: Round up to overestimate the cost. + cost = hyperdrive.convertToBase(cost.divUp(vaultSharePrice)); + } else { + // NOTE: Round up to overestimate the cost. + cost = cost.divUp(vaultSharePrice); + } } } From bde0843782bc440689e8d903ecd445324844960c Mon Sep 17 00:00:00 2001 From: Xiangyu Xu Date: Fri, 7 Mar 2025 12:18:58 -0800 Subject: [PATCH 5/7] Resolve PR feedback --- .../matching/HyperdriveMatchingEngineV2.sol | 12 ++++ .../MatchingEngineV2IntegrationTest.t.sol | 2 +- .../HyperdriveMatchingEngineV2Test.t.sol | 57 +------------------ 3 files changed, 16 insertions(+), 55 deletions(-) diff --git a/contracts/src/matching/HyperdriveMatchingEngineV2.sol b/contracts/src/matching/HyperdriveMatchingEngineV2.sol index a41dfa856..7114bde35 100644 --- a/contracts/src/matching/HyperdriveMatchingEngineV2.sol +++ b/contracts/src/matching/HyperdriveMatchingEngineV2.sol @@ -1459,6 +1459,18 @@ contract HyperdriveMatchingEngineV2 is uint256 governanceFee = 2 * flatFee.mulDown(config.fees.governanceLP); cost += governanceFee; + // @dev we can use hyperdrive.convertToBase and hyperdrive.convertToShares + // to simulate the conversions made within the yield source. When we + // are using base, what happens under the hood is that the calculations + // will flow as: + // 1. Deposit base into yield source. + // 2. Calculate the shares minted with hyperdrive.convertToShares(baseAmount) + // 3. Calculate the final base amount with + // finalBaseAmount = + // hyperdrive.convertToShares(baseAmount).mulDown(vaultSharePrice) + // With this in mind, we should be able to work back to this input + // base amount with high precision if we do: + // baseAmount = hyperdrive.convertToBase(finalBaseAmount.divDown(vaultSharePrice) if (_asBase) { // NOTE: Round up to overestimate the cost. cost = _hyperdrive.convertToBase(cost.divUp(vaultSharePrice)); diff --git a/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol b/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol index f5ca2ecf5..dca0daede 100644 --- a/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol +++ b/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.24; -import { IMorpho, Market, MarketParams, Id } from "morpho-blue/src/interfaces/IMorpho.sol"; +import { IMorpho, MarketParams } from "morpho-blue/src/interfaces/IMorpho.sol"; import { MarketParamsLib } from "morpho-blue/src/libraries/MarketParamsLib.sol"; import { MorphoBlueConversions } from "../../../contracts/src/instances/morpho-blue/MorphoBlueConversions.sol"; import { MorphoBlueHyperdrive } from "../../../contracts/src/instances/morpho-blue/MorphoBlueHyperdrive.sol"; diff --git a/test/units/matching/HyperdriveMatchingEngineV2Test.t.sol b/test/units/matching/HyperdriveMatchingEngineV2Test.t.sol index ac0cab25d..c722a5d01 100644 --- a/test/units/matching/HyperdriveMatchingEngineV2Test.t.sol +++ b/test/units/matching/HyperdriveMatchingEngineV2Test.t.sol @@ -575,9 +575,9 @@ contract HyperdriveMatchingEngineV2Test is HyperdriveTest { /// @dev Fuzzing test to verify TOKEN_AMOUNT_BUFFER is sufficient function testFuzz_tokenAmountBuffer(uint256 bondAmount) public { bondAmount = bound(bondAmount, 100e18, 1_000_000e18); - uint256 fundAmount1 = bondAmount / 2; - (, uint256 cost) = _calculateMintCost(bondAmount, true); - uint256 fundAmount2 = cost + 10 - fundAmount1; + // Ensure the fund is way more than sufficient + uint256 fundAmount1 = bondAmount * 2; + uint256 fundAmount2 = fundAmount1; // Create orders IHyperdriveMatchingEngineV2.OrderIntent @@ -1063,55 +1063,4 @@ contract HyperdriveMatchingEngineV2Test is HyperdriveTest { (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); return abi.encodePacked(r, s, v); } - - /// @dev Calculates the cost and parameters for minting positions. - /// @param _bondMatchAmount The amount of bonds to mint. - /// @param _asBase Whether the cost is in terms of base. - /// @return maturityTime The maturity time for new positions. - /// @return cost The total cost including fees. - function _calculateMintCost( - uint256 _bondMatchAmount, - bool _asBase - ) internal view returns (uint256 maturityTime, uint256 cost) { - // Get pool configuration. - IHyperdrive.PoolConfig memory config = hyperdrive.getPoolConfig(); - - // Calculate checkpoint and maturity time. - uint256 latestCheckpoint = hyperdrive.latestCheckpoint(); - maturityTime = latestCheckpoint + config.positionDuration; - - // Get vault share prices. - uint256 vaultSharePrice = hyperdrive.convertToBase(1e18); - uint256 openVaultSharePrice = hyperdrive - .getCheckpoint(latestCheckpoint) - .vaultSharePrice; - if (openVaultSharePrice == 0) { - openVaultSharePrice = vaultSharePrice; - } - - // Calculate the required fund amount. - // NOTE: Round the required fund amount up to overestimate the cost. - cost = _bondMatchAmount.mulDivUp( - vaultSharePrice.max(openVaultSharePrice), - openVaultSharePrice - ); - - // Add flat fee. - // NOTE: Round the flat fee calculation up to match other flows. - uint256 flatFee = _bondMatchAmount.mulUp(config.fees.flat); - cost += flatFee; - - // Add governance fee. - // NOTE: Round the governance fee calculation down to match other flows. - uint256 governanceFee = 2 * flatFee.mulDown(config.fees.governanceLP); - cost += governanceFee; - - if (_asBase) { - // NOTE: Round up to overestimate the cost. - cost = hyperdrive.convertToBase(cost.divUp(vaultSharePrice)); - } else { - // NOTE: Round up to overestimate the cost. - cost = cost.divUp(vaultSharePrice); - } - } } From 2a0538c3703b5de4836c9313b1d6fc14ab169439 Mon Sep 17 00:00:00 2001 From: Xiangyu Xu Date: Sun, 9 Mar 2025 00:55:28 -0800 Subject: [PATCH 6/7] Finished fork testing with Renzo and Moonwell --- .../MatchingEngineV2IntegrationTest.t.sol | 227 ++++++++ .../MoonwellUSDCMatchingEngineTest.t.sol | 527 ++++++++++++++++++ 2 files changed, 754 insertions(+) create mode 100644 test/integrations/matching/MoonwellUSDCMatchingEngineTest.t.sol diff --git a/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol b/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol index dca0daede..8b6282bdc 100644 --- a/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol +++ b/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol @@ -20,6 +20,14 @@ import { HyperdriveMatchingEngineV2 } from "../../../contracts/src/matching/Hype import { HyperdriveTest } from "../../utils/HyperdriveTest.sol"; import { HyperdriveUtils } from "../../utils/HyperdriveUtils.sol"; import { Lib } from "../../utils/Lib.sol"; +import { IRestakeManager, IRenzoOracle } from "../../../contracts/src/interfaces/IRenzo.sol"; +import { EzETHHyperdrive } from "../../../contracts/src/instances/ezETH/EzETHHyperdrive.sol"; +import { EzETHTarget0 } from "../../../contracts/src/instances/ezETH/EzETHTarget0.sol"; +import { EzETHTarget1 } from "../../../contracts/src/instances/ezETH/EzETHTarget1.sol"; +import { EzETHTarget2 } from "../../../contracts/src/instances/ezETH/EzETHTarget2.sol"; +import { EzETHTarget3 } from "../../../contracts/src/instances/ezETH/EzETHTarget3.sol"; +import { EzETHTarget4 } from "../../../contracts/src/instances/ezETH/EzETHTarget4.sol"; +import { EzETHConversions } from "../../../contracts/src/instances/ezETH/EzETHConversions.sol"; /// @dev This test suite tests if TOKEN_AMOUNT_BUFFER in HyperdriveMatchingEngineV2 /// is sufficient for successful minting operations across different pools. @@ -77,6 +85,24 @@ contract TokenBufferTest is HyperdriveTest { /// @dev The wstETH/USDC Hyperdrive instance IHyperdrive internal wstethUsdcHyperdrive; + /// @dev The Renzo RestakeManager contract + IRestakeManager internal constant RESTAKE_MANAGER = + IRestakeManager(0x74a09653A083691711cF8215a6ab074BB4e99ef5); + + /// @dev The Renzo Oracle contract + IRenzoOracle internal constant RENZO_ORACLE = + IRenzoOracle(0x5a12796f7e7EBbbc8a402667d266d2e65A814042); + + /// @dev The ezETH token contract + address internal constant EZETH = + 0xbf5495Efe5DB9ce00f80364C8B423567e58d2110; + + /// @dev The placeholder address for ETH. + address internal constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + /// @dev The EzETH Hyperdrive instance + IHyperdrive internal ezethHyperdrive; + /// @dev Sets up the test harness on a mainnet fork function setUp() public override __mainnet_fork(21_931_000) { // Run the higher-level setup logic @@ -244,6 +270,78 @@ contract TokenBufferTest is HyperdriveTest { ) ); + // Deploy an EzETH Hyperdrive pool + IHyperdrive.PoolConfig memory ezethConfig = testConfig( + 0.04e18, + 15 days + ); + ezethConfig.baseToken = IERC20(ETH); + ezethConfig.vaultSharesToken = IERC20(EZETH); + ezethConfig.fees.curve = 0.01e18; + ezethConfig.fees.flat = 0.0005e18; + ezethConfig.fees.governanceLP = 0.15e18; + ezethConfig.minimumShareReserves = 1e15; + ezethConfig.minimumTransactionAmount = 1e15; + + // Use EzETHConversions to calculate the initialVaultSharePrice + ezethConfig.initialVaultSharePrice = EzETHConversions.convertToBase( + RENZO_ORACLE, + RESTAKE_MANAGER, + IERC20(EZETH), + ONE + ); + + emit log_named_uint( + "EzETH Initial vault share price", + ezethConfig.initialVaultSharePrice + ); + + ezethHyperdrive = IHyperdrive( + address( + new EzETHHyperdrive( + "EzETHHyperdrive", + ezethConfig, + adminController, + address( + new EzETHTarget0( + ezethConfig, + adminController, + RESTAKE_MANAGER + ) + ), + address( + new EzETHTarget1( + ezethConfig, + adminController, + RESTAKE_MANAGER + ) + ), + address( + new EzETHTarget2( + ezethConfig, + adminController, + RESTAKE_MANAGER + ) + ), + address( + new EzETHTarget3( + ezethConfig, + adminController, + RESTAKE_MANAGER + ) + ), + address( + new EzETHTarget4( + ezethConfig, + adminController, + RESTAKE_MANAGER + ) + ), + RESTAKE_MANAGER + ) + ) + ); + // Fund accounts with tokens deal(DAI, alice, 10_000_000e18); deal(USDE, alice, 10_000_000e18); @@ -260,6 +358,10 @@ contract TokenBufferTest is HyperdriveTest { deal(USDC, INITIALIZER, 10_000_000e6); deal(WSTETH, INITIALIZER, 1000e18); + deal(EZETH, alice, 10_000e18); + deal(EZETH, bob, 10_000e18); + deal(EZETH, INITIALIZER, 10_000e18); + // Approve tokens _approveTokens(alice, address(usdeDaiHyperdrive), IERC20(DAI)); _approveTokens(alice, address(usdeDaiHyperdrive), IERC20(USDE)); @@ -292,6 +394,12 @@ contract TokenBufferTest is HyperdriveTest { IERC20(WSTETH) ); + _approveTokens(alice, address(ezethHyperdrive), IERC20(EZETH)); + _approveTokens(bob, address(ezethHyperdrive), IERC20(EZETH)); + _approveTokens(INITIALIZER, address(ezethHyperdrive), IERC20(EZETH)); + _approveTokens(alice, address(matchingEngine), IERC20(EZETH)); + _approveTokens(bob, address(matchingEngine), IERC20(EZETH)); + // Initialize the Hyperdrive pools vm.startPrank(INITIALIZER); @@ -317,6 +425,17 @@ contract TokenBufferTest is HyperdriveTest { }) ); + // Initialize EzETH Hyperdrive pool + ezethHyperdrive.initialize( + 1_000e18, // 1000 ezETH + 0.0361e18, + IHyperdrive.Options({ + asBase: false, + destination: INITIALIZER, + extraData: "" + }) + ); + vm.stopPrank(); } @@ -546,6 +665,114 @@ contract TokenBufferTest is HyperdriveTest { ); } + /// @dev Tests if TOKEN_AMOUNT_BUFFER is sufficient for EzETH pool. + /// This test uses fixed time and iterates through increasing bond amounts. + function test_tokenBuffer_EzETH_notAsBase() external { + // Use a fixed time elapsed + uint256 timeElapsed = 1 days; + + // Advance time to simulate real-world conditions + vm.warp(block.timestamp + timeElapsed); + + // Initialize counters for this test + uint256 localSuccessfulTrades = 0; + uint256 localFailedTrades = 0; + + // Start with 2e18 and increment by 5e18 each time, for 20 iterations + for (uint256 i = 0; i < 20; i++) { + uint256 bondAmount = 2e18 + (i * 5e18); + + // Calculate fund amounts and make it more than sufficient + uint256 totalFunds = 2 * bondAmount; + + // Split funds between Alice and Bob + uint256 aliceFundAmount = totalFunds / 2; + uint256 bobFundAmount = totalFunds - aliceFundAmount; + + // Create orders + IHyperdriveMatchingEngineV2.OrderIntent + memory longOrder = _createOrderIntent( + alice, + bob, + ezethHyperdrive, + aliceFundAmount, + bondAmount, + false, // asBase + IHyperdriveMatchingEngineV2.OrderType.OpenLong + ); + + IHyperdriveMatchingEngineV2.OrderIntent + memory shortOrder = _createOrderIntent( + bob, + alice, + ezethHyperdrive, + bobFundAmount, + bondAmount, + false, // asBase + IHyperdriveMatchingEngineV2.OrderType.OpenShort + ); + + // Sign orders + longOrder.signature = _signOrderIntent(longOrder, alicePK); + shortOrder.signature = _signOrderIntent(shortOrder, bobPK); + + // Try to match orders and record success/failure + try matchingEngine.matchOrders(longOrder, shortOrder, CHARLIE) { + localSuccessfulTrades++; + + // Log successful trade + emit log_named_uint( + "Successful trade with bond amount", + bondAmount + ); + + // Verify positions were created + uint256 aliceLongBalance = ezethHyperdrive.balanceOf( + AssetId.encodeAssetId( + AssetId.AssetIdPrefix.Long, + ezethHyperdrive.latestCheckpoint() + + ezethHyperdrive.getPoolConfig().positionDuration + ), + alice + ); + + uint256 bobShortBalance = ezethHyperdrive.balanceOf( + AssetId.encodeAssetId( + AssetId.AssetIdPrefix.Short, + ezethHyperdrive.latestCheckpoint() + + ezethHyperdrive.getPoolConfig().positionDuration + ), + bob + ); + + assertGt( + aliceLongBalance, + 0, + "Alice should have long position" + ); + assertGt(bobShortBalance, 0, "Bob should have short position"); + } catch { + localFailedTrades++; + + // Log failed trade + emit log_named_uint( + "Failed trade with bond amount", + bondAmount + ); + } + + // Advance block to avoid nonce issues + vm.roll(block.number + 1); + } + + // Log summary + emit log_named_uint( + "EzETH - Total successful trades", + localSuccessfulTrades + ); + emit log_named_uint("EzETH - Total failed trades", localFailedTrades); + } + /// @dev Helper function to create an order intent function _createOrderIntent( address _trader, diff --git a/test/integrations/matching/MoonwellUSDCMatchingEngineTest.t.sol b/test/integrations/matching/MoonwellUSDCMatchingEngineTest.t.sol new file mode 100644 index 000000000..1c2f6a8ab --- /dev/null +++ b/test/integrations/matching/MoonwellUSDCMatchingEngineTest.t.sol @@ -0,0 +1,527 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +import { IERC20 } from "../../../contracts/src/interfaces/IERC20.sol"; +import { IERC4626 } from "../../../contracts/src/interfaces/IERC4626.sol"; +import { IHyperdrive } from "../../../contracts/src/interfaces/IHyperdrive.sol"; +import { IHyperdriveMatchingEngineV2 } from "../../../contracts/src/interfaces/IHyperdriveMatchingEngineV2.sol"; +import { AssetId } from "../../../contracts/src/libraries/AssetId.sol"; +import { FixedPointMath, ONE } from "../../../contracts/src/libraries/FixedPointMath.sol"; +import { HyperdriveMatchingEngineV2 } from "../../../contracts/src/matching/HyperdriveMatchingEngineV2.sol"; +import { HyperdriveTest } from "../../utils/HyperdriveTest.sol"; +import { HyperdriveUtils } from "../../utils/HyperdriveUtils.sol"; +import { Lib } from "../../utils/Lib.sol"; +import { ERC4626Hyperdrive } from "../../../contracts/src/instances/erc4626/ERC4626Hyperdrive.sol"; +import { ERC4626Target0 } from "../../../contracts/src/instances/erc4626/ERC4626Target0.sol"; +import { ERC4626Target1 } from "../../../contracts/src/instances/erc4626/ERC4626Target1.sol"; +import { ERC4626Target2 } from "../../../contracts/src/instances/erc4626/ERC4626Target2.sol"; +import { ERC4626Target3 } from "../../../contracts/src/instances/erc4626/ERC4626Target3.sol"; +import { ERC4626Target4 } from "../../../contracts/src/instances/erc4626/ERC4626Target4.sol"; +import { ERC4626Conversions } from "../../../contracts/src/instances/erc4626/ERC4626Conversions.sol"; + +/// @dev This test suite tests if TOKEN_AMOUNT_BUFFER in HyperdriveMatchingEngineV2 +/// is sufficient for successful minting operations with Moonwell USDC pool. +contract MoonwellUSDCMatchingEngineTest is HyperdriveTest { + using FixedPointMath for *; + using HyperdriveUtils for *; + using Lib for *; + + /// @dev Charlie will be the surplus recipient + address internal constant CHARLIE = + 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC; + + /// @dev The initializer of the pools + address internal constant INITIALIZER = + 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; + + /// @dev The USDC token address on Base (6 decimals) + address internal constant BASE_USDC = + 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; + + /// @dev The Moonwell USDC token address (18 decimals) + address internal constant MWUSDC = + 0xc1256Ae5FF1cf2719D4937adb3bbCCab2E00A2Ca; + + /// @dev The decimal difference between MWUSDC (18) and BASE_USDC (6) + uint256 internal constant DECIMAL_DIFF = 12; // 18 - 6 = 12 + + /// @dev The Hyperdrive matching engine that is deployed + IHyperdriveMatchingEngineV2 internal matchingEngine; + + /// @dev The Moonwell USDC Hyperdrive instance + IHyperdrive internal moonwellUsdcHyperdrive; + + /// @dev Sets up the test harness on a Base fork + function setUp() public override __base_fork(21_117_198) { + // Run the higher-level setup logic + super.setUp(); + + // Deploy the Hyperdrive matching engine + matchingEngine = IHyperdriveMatchingEngineV2( + address( + new HyperdriveMatchingEngineV2("Hyperdrive Matching Engine V2") + ) + ); + + emit log_string("Deployed matching engine"); + + try this.deployMoonwellPool() { + emit log_string("Successfully deployed Moonwell USDC pool"); + } catch Error(string memory reason) { + emit log_string("Failed to deploy Moonwell USDC pool"); + emit log_string(reason); + revert(reason); + } catch (bytes memory lowLevelData) { + emit log_string( + "Failed to deploy Moonwell USDC pool with low level error" + ); + emit log_bytes(lowLevelData); + revert("Low level error in deployMoonwellPool"); + } + + try this.fundAccounts() { + emit log_string("Successfully funded accounts"); + } catch Error(string memory reason) { + emit log_string("Failed to fund accounts"); + emit log_string(reason); + revert(reason); + } catch (bytes memory lowLevelData) { + emit log_string("Failed to fund accounts with low level error"); + emit log_bytes(lowLevelData); + revert("Low level error in fundAccounts"); + } + + try this.approveTokens() { + emit log_string("Successfully approved tokens"); + } catch Error(string memory reason) { + emit log_string("Failed to approve tokens"); + emit log_string(reason); + revert(reason); + } catch (bytes memory lowLevelData) { + emit log_string("Failed to approve tokens with low level error"); + emit log_bytes(lowLevelData); + revert("Low level error in approveTokens"); + } + + try this.initializePool() { + emit log_string("Successfully initialized Moonwell USDC pool"); + } catch Error(string memory reason) { + emit log_string("Failed to initialize Moonwell USDC pool"); + emit log_string(reason); + revert(reason); + } catch (bytes memory lowLevelData) { + emit log_string( + "Failed to initialize Moonwell USDC pool with low level error" + ); + emit log_bytes(lowLevelData); + revert("Low level error in initializePool"); + } + } + + /// @dev Helper function to deploy the Moonwell USDC pool + function deployMoonwellPool() external { + // Deploy a Moonwell USDC Hyperdrive pool + IHyperdrive.PoolConfig memory moonwellConfig = testConfig( + 0.04e18, + 182 days + ); + moonwellConfig.baseToken = IERC20(BASE_USDC); + moonwellConfig.vaultSharesToken = IERC20(MWUSDC); + moonwellConfig.fees.curve = 0.001e18; + moonwellConfig.fees.flat = 0.0001e18; + moonwellConfig.fees.governanceLP = 0; + moonwellConfig.minimumShareReserves = 1e3 * (10 ** DECIMAL_DIFF); // 1e15 for MWUSDC + moonwellConfig.minimumTransactionAmount = 1e3; + + // Use ERC4626Conversions to calculate the initialVaultSharePrice + // This is similar to how MorphoBlueConversions is used in the MorphoBlue tests + moonwellConfig.initialVaultSharePrice = ERC4626Conversions + .convertToBase(IERC4626(address(MWUSDC)), ONE); + + emit log_named_uint( + "Initial vault share price", + moonwellConfig.initialVaultSharePrice + ); + + moonwellUsdcHyperdrive = IHyperdrive( + address( + new ERC4626Hyperdrive( + "MoonwellUSDCHyperdrive", + moonwellConfig, + adminController, + address( + new ERC4626Target0(moonwellConfig, adminController) + ), + address( + new ERC4626Target1(moonwellConfig, adminController) + ), + address( + new ERC4626Target2(moonwellConfig, adminController) + ), + address( + new ERC4626Target3(moonwellConfig, adminController) + ), + address(new ERC4626Target4(moonwellConfig, adminController)) + ) + ) + ); + } + + /// @dev Helper function to fund accounts with tokens + function fundAccounts() external { + // Fund accounts with tokens for Moonwell + deal(BASE_USDC, alice, 10_000_000_000e6); + emit log_string("Funded alice with BASE_USDC"); + + deal(BASE_USDC, bob, 10_000_000_000e6); + emit log_string("Funded bob with BASE_USDC"); + + deal(BASE_USDC, INITIALIZER, 10_000_000_000e6); + emit log_string("Funded INITIALIZER with BASE_USDC"); + + deal(MWUSDC, alice, 10_000_000_000e18); + emit log_string("Funded alice with MWUSDC"); + + deal(MWUSDC, bob, 10_000_000_000e18); + emit log_string("Funded bob with MWUSDC"); + + deal(MWUSDC, INITIALIZER, 10_000_000_000e18); + emit log_string("Funded INITIALIZER with MWUSDC"); + } + + /// @dev Helper function to approve tokens + function approveTokens() external { + // Approve tokens for Moonwell + _approveTokens( + alice, + address(moonwellUsdcHyperdrive), + IERC20(BASE_USDC) + ); + _approveTokens(bob, address(moonwellUsdcHyperdrive), IERC20(BASE_USDC)); + _approveTokens( + INITIALIZER, + address(moonwellUsdcHyperdrive), + IERC20(BASE_USDC) + ); + _approveTokens(alice, address(moonwellUsdcHyperdrive), IERC20(MWUSDC)); + _approveTokens(bob, address(moonwellUsdcHyperdrive), IERC20(MWUSDC)); + _approveTokens( + INITIALIZER, + address(moonwellUsdcHyperdrive), + IERC20(MWUSDC) + ); + _approveTokens(alice, address(matchingEngine), IERC20(BASE_USDC)); + _approveTokens(bob, address(matchingEngine), IERC20(BASE_USDC)); + _approveTokens(alice, address(matchingEngine), IERC20(MWUSDC)); + _approveTokens(bob, address(matchingEngine), IERC20(MWUSDC)); + } + + /// @dev Helper function to initialize the Moonwell USDC pool + function initializePool() external { + // Initialize Moonwell USDC pool + vm.startPrank(INITIALIZER); + moonwellUsdcHyperdrive.initialize( + 1_000_000e6, // 1M USDC + 0.0361e18, + IHyperdrive.Options({ + asBase: true, + destination: INITIALIZER, + extraData: "" + }) + ); + vm.stopPrank(); + } + + /// @dev Tests if TOKEN_AMOUNT_BUFFER is sufficient for Moonwell USDC pool with asBase=true. + function test_tokenBuffer_Moonwell_USDC_asBase() external { + // Use a fixed time elapsed + uint256 timeElapsed = 1 days; + + // Advance time to simulate real-world conditions + vm.warp(block.timestamp + timeElapsed); + + // Initialize counters for this test + uint256 localSuccessfulTrades = 0; + uint256 localFailedTrades = 0; + + // Start with 2000e6 and increment by 50000e6 each time, for 20 iterations + for (uint256 i = 0; i < 20; i++) { + uint256 bondAmount = 2000e6 + (i * 50000e6); + + // Calculate fund amounts and make it more than sufficient + uint256 totalFunds = 2 * bondAmount; + + // Split funds between Alice and Bob + uint256 aliceFundAmount = totalFunds / 2; + uint256 bobFundAmount = totalFunds - aliceFundAmount; + + // Create orders + IHyperdriveMatchingEngineV2.OrderIntent + memory longOrder = _createOrderIntent( + alice, + bob, + moonwellUsdcHyperdrive, + aliceFundAmount, + bondAmount, + true, // asBase + IHyperdriveMatchingEngineV2.OrderType.OpenLong + ); + + IHyperdriveMatchingEngineV2.OrderIntent + memory shortOrder = _createOrderIntent( + bob, + alice, + moonwellUsdcHyperdrive, + bobFundAmount, + bondAmount, + true, // asBase + IHyperdriveMatchingEngineV2.OrderType.OpenShort + ); + + // Sign orders + longOrder.signature = _signOrderIntent(longOrder, alicePK); + shortOrder.signature = _signOrderIntent(shortOrder, bobPK); + + // matchingEngine.matchOrders(longOrder, shortOrder, CHARLIE); + + // Try to match orders and record success/failure + try matchingEngine.matchOrders(longOrder, shortOrder, CHARLIE) { + localSuccessfulTrades++; + + // Log successful trade + emit log_named_uint( + "Successful trade with bond amount", + bondAmount + ); + + // Verify positions were created + uint256 aliceLongBalance = moonwellUsdcHyperdrive.balanceOf( + AssetId.encodeAssetId( + AssetId.AssetIdPrefix.Long, + moonwellUsdcHyperdrive.latestCheckpoint() + + moonwellUsdcHyperdrive + .getPoolConfig() + .positionDuration + ), + alice + ); + + uint256 bobShortBalance = moonwellUsdcHyperdrive.balanceOf( + AssetId.encodeAssetId( + AssetId.AssetIdPrefix.Short, + moonwellUsdcHyperdrive.latestCheckpoint() + + moonwellUsdcHyperdrive + .getPoolConfig() + .positionDuration + ), + bob + ); + + assertGt( + aliceLongBalance, + 0, + "Alice should have long position" + ); + assertGt(bobShortBalance, 0, "Bob should have short position"); + } catch { + localFailedTrades++; + + // Log failed trade + emit log_named_uint( + "Failed trade with bond amount", + bondAmount + ); + } + + // Advance block to avoid nonce issues + vm.roll(block.number + 1); + } + + // Log summary + emit log_named_uint( + "Moonwell USDC (asBase=true) - Total successful trades", + localSuccessfulTrades + ); + emit log_named_uint( + "Moonwell USDC (asBase=true) - Total failed trades", + localFailedTrades + ); + } + + /// @dev Tests if TOKEN_AMOUNT_BUFFER is sufficient for Moonwell USDC pool with asBase=false. + function test_tokenBuffer_Moonwell_USDC_notAsBase() external { + // Use a fixed time elapsed + uint256 timeElapsed = 1 days; + + // Advance time to simulate real-world conditions + vm.warp(block.timestamp + timeElapsed); + + // Initialize counters for this test + uint256 localSuccessfulTrades = 0; + uint256 localFailedTrades = 0; + + // Start with 2000e6 and increment by 50000e6 each time, for 20 iterations + for (uint256 i = 0; i < 20; i++) { + uint256 bondAmount = 2000e6 + (i * 50000e6); + + // Calculate fund amounts and make it more than sufficient + uint256 totalFunds = 5 * bondAmount * (10 ** DECIMAL_DIFF); + + // Split funds between Alice and Bob + uint256 aliceFundAmount = totalFunds / 2; + uint256 bobFundAmount = totalFunds - aliceFundAmount; + + // Create orders + IHyperdriveMatchingEngineV2.OrderIntent + memory longOrder = _createOrderIntent( + alice, + bob, + moonwellUsdcHyperdrive, + aliceFundAmount, + bondAmount, + false, // asBase + IHyperdriveMatchingEngineV2.OrderType.OpenLong + ); + + IHyperdriveMatchingEngineV2.OrderIntent + memory shortOrder = _createOrderIntent( + bob, + alice, + moonwellUsdcHyperdrive, + bobFundAmount, + bondAmount, + false, // asBase + IHyperdriveMatchingEngineV2.OrderType.OpenShort + ); + + // Sign orders + longOrder.signature = _signOrderIntent(longOrder, alicePK); + shortOrder.signature = _signOrderIntent(shortOrder, bobPK); + + // Try to match orders and record success/failure + try matchingEngine.matchOrders(longOrder, shortOrder, CHARLIE) { + localSuccessfulTrades++; + + // Log successful trade + emit log_named_uint( + "Successful trade with bond amount", + bondAmount + ); + + // Verify positions were created + uint256 aliceLongBalance = moonwellUsdcHyperdrive.balanceOf( + AssetId.encodeAssetId( + AssetId.AssetIdPrefix.Long, + moonwellUsdcHyperdrive.latestCheckpoint() + + moonwellUsdcHyperdrive + .getPoolConfig() + .positionDuration + ), + alice + ); + + uint256 bobShortBalance = moonwellUsdcHyperdrive.balanceOf( + AssetId.encodeAssetId( + AssetId.AssetIdPrefix.Short, + moonwellUsdcHyperdrive.latestCheckpoint() + + moonwellUsdcHyperdrive + .getPoolConfig() + .positionDuration + ), + bob + ); + + assertGt( + aliceLongBalance, + 0, + "Alice should have long position" + ); + assertGt(bobShortBalance, 0, "Bob should have short position"); + } catch { + localFailedTrades++; + + // Log failed trade + emit log_named_uint( + "Failed trade with bond amount", + bondAmount + ); + } + + // Advance block to avoid nonce issues + vm.roll(block.number + 1); + } + + // Log summary + emit log_named_uint( + "Moonwell USDC (asBase=false) - Total successful trades", + localSuccessfulTrades + ); + emit log_named_uint( + "Moonwell USDC (asBase=false) - Total failed trades", + localFailedTrades + ); + } + + /// @dev Helper function to create an order intent + function _createOrderIntent( + address _trader, + address _counterparty, + IHyperdrive _hyperdrive, + uint256 _fundAmount, + uint256 _bondAmount, + bool _asBase, + IHyperdriveMatchingEngineV2.OrderType _orderType + ) internal view returns (IHyperdriveMatchingEngineV2.OrderIntent memory) { + return + IHyperdriveMatchingEngineV2.OrderIntent({ + trader: _trader, + counterparty: _counterparty, + hyperdrive: _hyperdrive, + fundAmount: _fundAmount, + bondAmount: _bondAmount, + minVaultSharePrice: 0, + options: IHyperdrive.Options({ + asBase: _asBase, + destination: _trader, + extraData: "" + }), + orderType: _orderType, + minMaturityTime: 0, + maxMaturityTime: type(uint256).max, + expiry: block.timestamp + 1 hours, + salt: bytes32( + uint256( + keccak256( + abi.encodePacked( + _trader, + _orderType, + block.timestamp + ) + ) + ) + ), + signature: new bytes(0) + }); + } + + /// @dev Helper function to sign an order intent + function _signOrderIntent( + IHyperdriveMatchingEngineV2.OrderIntent memory _order, + uint256 _privateKey + ) internal view returns (bytes memory) { + bytes32 orderHash = matchingEngine.hashOrderIntent(_order); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(_privateKey, orderHash); + return abi.encodePacked(r, s, v); + } + + /// @dev Helper function to approve tokens + function _approveTokens( + address _owner, + address _spender, + IERC20 _token + ) internal { + vm.startPrank(_owner); + _token.approve(_spender, type(uint256).max); + vm.stopPrank(); + } +} From 9d52d8c6e27bd79b0e79a90e538f19a43a13a9d2 Mon Sep 17 00:00:00 2001 From: Xiangyu Xu Date: Sun, 9 Mar 2025 01:16:25 -0800 Subject: [PATCH 7/7] fixed typos --- .../matching/MatchingEngineV2IntegrationTest.t.sol | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol b/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol index 8b6282bdc..1e0c17084 100644 --- a/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol +++ b/test/integrations/matching/MatchingEngineV2IntegrationTest.t.sol @@ -21,13 +21,13 @@ import { HyperdriveTest } from "../../utils/HyperdriveTest.sol"; import { HyperdriveUtils } from "../../utils/HyperdriveUtils.sol"; import { Lib } from "../../utils/Lib.sol"; import { IRestakeManager, IRenzoOracle } from "../../../contracts/src/interfaces/IRenzo.sol"; -import { EzETHHyperdrive } from "../../../contracts/src/instances/ezETH/EzETHHyperdrive.sol"; -import { EzETHTarget0 } from "../../../contracts/src/instances/ezETH/EzETHTarget0.sol"; -import { EzETHTarget1 } from "../../../contracts/src/instances/ezETH/EzETHTarget1.sol"; -import { EzETHTarget2 } from "../../../contracts/src/instances/ezETH/EzETHTarget2.sol"; -import { EzETHTarget3 } from "../../../contracts/src/instances/ezETH/EzETHTarget3.sol"; -import { EzETHTarget4 } from "../../../contracts/src/instances/ezETH/EzETHTarget4.sol"; -import { EzETHConversions } from "../../../contracts/src/instances/ezETH/EzETHConversions.sol"; +import { EzETHHyperdrive } from "../../../contracts/src/instances/ezeth/EzETHHyperdrive.sol"; +import { EzETHTarget0 } from "../../../contracts/src/instances/ezeth/EzETHTarget0.sol"; +import { EzETHTarget1 } from "../../../contracts/src/instances/ezeth/EzETHTarget1.sol"; +import { EzETHTarget2 } from "../../../contracts/src/instances/ezeth/EzETHTarget2.sol"; +import { EzETHTarget3 } from "../../../contracts/src/instances/ezeth/EzETHTarget3.sol"; +import { EzETHTarget4 } from "../../../contracts/src/instances/ezeth/EzETHTarget4.sol"; +import { EzETHConversions } from "../../../contracts/src/instances/ezeth/EzETHConversions.sol"; /// @dev This test suite tests if TOKEN_AMOUNT_BUFFER in HyperdriveMatchingEngineV2 /// is sufficient for successful minting operations across different pools.