From 8165929e48da3677331c6202ad752f40b7200926 Mon Sep 17 00:00:00 2001 From: Alex Towle Date: Thu, 28 Sep 2023 17:29:07 -0500 Subject: [PATCH 1/3] Added a `minSharePrice` --- contracts/src/HyperdriveLong.sol | 7 ++++ contracts/src/HyperdriveShort.sol | 7 ++++ contracts/src/interfaces/IHyperdrive.sol | 3 ++ contracts/src/interfaces/IHyperdriveWrite.sol | 2 ++ script/DevnetSmokeTest.s.sol | 5 ++- test/integrations/ERC4626Validation.t.sol | 4 +++ .../NegativeInterestLongFeeTest.t.sol | 3 ++ .../NegativeInterestShortFeeTest.t.sol | 2 ++ .../hyperdrive/ReentrancyTest.t.sol | 9 +++-- test/units/hyperdrive/CloseShortTest.t.sol | 4 +++ test/units/hyperdrive/ExtremeInputs.t.sol | 2 +- test/units/hyperdrive/FeeTest.t.sol | 4 +++ test/units/hyperdrive/OpenLongTest.t.sol | 36 +++++++++++++++---- test/units/hyperdrive/OpenShortTest.t.sol | 9 ++--- test/units/libraries/HyperdriveMath.t.sol | 4 +-- test/utils/HyperdriveTest.sol | 15 ++++++++ 16 files changed, 100 insertions(+), 16 deletions(-) diff --git a/contracts/src/HyperdriveLong.sol b/contracts/src/HyperdriveLong.sol index 71dc7eb55..dd8cffc4d 100644 --- a/contracts/src/HyperdriveLong.sol +++ b/contracts/src/HyperdriveLong.sol @@ -23,6 +23,9 @@ abstract contract HyperdriveLong is HyperdriveLP { /// @notice Opens a long position. /// @param _baseAmount The amount of base to use when trading. /// @param _minOutput The minium number of bonds to receive. + /// @param _minSharePrice The minium share price at which to open the long. + /// This allows traders to protect themselves from opening a long in + /// a checkpoint where negative interest has accrued. /// @param _destination The address which will receive the bonds /// @param _asUnderlying A flag indicating whether the sender will pay in /// base or using another currency. Implementations choose which @@ -32,6 +35,7 @@ abstract contract HyperdriveLong is HyperdriveLP { function openLong( uint256 _baseAmount, uint256 _minOutput, + uint256 _minSharePrice, address _destination, bool _asUnderlying ) @@ -52,6 +56,9 @@ abstract contract HyperdriveLong is HyperdriveLP { _baseAmount, _asUnderlying ); + if (sharePrice < _minSharePrice) { + revert IHyperdrive.MinimumSharePrice(); + } // Perform a checkpoint. uint256 latestCheckpoint = _latestCheckpoint(); diff --git a/contracts/src/HyperdriveShort.sol b/contracts/src/HyperdriveShort.sol index e3627494f..9c8d6b063 100644 --- a/contracts/src/HyperdriveShort.sol +++ b/contracts/src/HyperdriveShort.sol @@ -23,6 +23,9 @@ abstract contract HyperdriveShort is HyperdriveLP { /// @notice Opens a short position. /// @param _bondAmount The amount of bonds to short. /// @param _maxDeposit The most the user expects to deposit for this trade + /// @param _minSharePrice The minium share price at which to open the long. + /// This allows traders to protect themselves from opening a long in + /// a checkpoint where negative interest has accrued. /// @param _destination The address which gets credited with share tokens /// @param _asUnderlying A flag indicating whether the sender will pay in /// base or using another currency. Implementations choose which @@ -32,6 +35,7 @@ abstract contract HyperdriveShort is HyperdriveLP { function openShort( uint256 _bondAmount, uint256 _maxDeposit, + uint256 _minSharePrice, address _destination, bool _asUnderlying ) @@ -52,6 +56,9 @@ abstract contract HyperdriveShort is HyperdriveLP { // Since the short will receive interest from the beginning of the // checkpoint, they will receive this backdated interest back at closing. uint256 sharePrice = _pricePerShare(); + if (sharePrice < _minSharePrice) { + revert IHyperdrive.MinimumSharePrice(); + } uint256 latestCheckpoint = _latestCheckpoint(); uint256 openSharePrice = _applyCheckpoint(latestCheckpoint, sharePrice); diff --git a/contracts/src/interfaces/IHyperdrive.sol b/contracts/src/interfaces/IHyperdrive.sol index 4ab869ec8..35d11e08a 100644 --- a/contracts/src/interfaces/IHyperdrive.sol +++ b/contracts/src/interfaces/IHyperdrive.sol @@ -202,6 +202,8 @@ interface IHyperdrive is IHyperdriveRead, IHyperdriveWrite, IMultiToken { /// ### Hyperdrive ### /// ################## error ApprovalFailed(); + // TODO: We should rename this so that it's clear that it pertains to + // solvency. error BaseBufferExceedsShareReserves(); error BelowMinimumContribution(); error BelowMinimumShareReserves(); @@ -227,6 +229,7 @@ interface IHyperdrive is IHyperdriveRead, IHyperdriveWrite, IMultiToken { error UnexpectedAssetId(); error UnexpectedSender(); error UnsupportedToken(); + error MinimumSharePrice(); error MinimumTransactionAmount(); error ZeroLpTotalSupply(); diff --git a/contracts/src/interfaces/IHyperdriveWrite.sol b/contracts/src/interfaces/IHyperdriveWrite.sol index fc08f655a..0ea2f6e75 100644 --- a/contracts/src/interfaces/IHyperdriveWrite.sol +++ b/contracts/src/interfaces/IHyperdriveWrite.sol @@ -42,6 +42,7 @@ interface IHyperdriveWrite is IMultiTokenWrite { function openLong( uint256 _baseAmount, uint256 _minOutput, + uint256 _minSharePrice, address _destination, bool _asUnderlying ) external payable returns (uint256 maturityTime, uint256 bondProceeds); @@ -57,6 +58,7 @@ interface IHyperdriveWrite is IMultiTokenWrite { function openShort( uint256 _bondAmount, uint256 _maxDeposit, + uint256 _minSharePrice, address _destination, bool _asUnderlying ) external payable returns (uint256 maturityTime, uint256 traderDeposit); diff --git a/script/DevnetSmokeTest.s.sol b/script/DevnetSmokeTest.s.sol index b13f1bfa0..00301eee2 100644 --- a/script/DevnetSmokeTest.s.sol +++ b/script/DevnetSmokeTest.s.sol @@ -42,6 +42,7 @@ contract DevnetSmokeTest is Script { (, uint256 basePaid) = HYPERDRIVE.openShort( bondAmount, type(uint256).max, + 0, msg.sender, true ); @@ -52,7 +53,7 @@ contract DevnetSmokeTest is Script { uint256 basePaid = 300_000e18; BASE.mint(msg.sender, basePaid); BASE.approve(address(HYPERDRIVE), basePaid); - HYPERDRIVE.openLong(basePaid, 0, msg.sender, true); + HYPERDRIVE.openLong(basePaid, 0, 0, msg.sender, true); } console.log("Ending pool info:"); @@ -70,6 +71,7 @@ contract DevnetSmokeTest is Script { (uint256 maturityTime, uint256 bondAmount) = HYPERDRIVE.openLong( 10_000e18, 0, + 0, msg.sender, true ); @@ -134,6 +136,7 @@ contract DevnetSmokeTest is Script { (uint256 maturityTime, uint256 bondAmount) = HYPERDRIVE.openShort( 10_000e18, type(uint256).max, + 0, msg.sender, true ); diff --git a/test/integrations/ERC4626Validation.t.sol b/test/integrations/ERC4626Validation.t.sol index d9f652ea7..9ab158f2c 100644 --- a/test/integrations/ERC4626Validation.t.sol +++ b/test/integrations/ERC4626Validation.t.sol @@ -510,6 +510,7 @@ abstract contract ERC4626ValidationTest is HyperdriveTest { (maturityTime, bondAmount) = hyperdrive.openLong( baseAmount, 0, + 0, trader, asUnderlying ); @@ -518,6 +519,7 @@ abstract contract ERC4626ValidationTest is HyperdriveTest { (maturityTime, bondAmount) = hyperdrive.openLong( baseAmount, 0, + 0, trader, asUnderlying ); @@ -539,6 +541,7 @@ abstract contract ERC4626ValidationTest is HyperdriveTest { (maturityTime, baseAmount) = hyperdrive.openShort( bondAmount, type(uint256).max, + 0, trader, asUnderlying ); @@ -547,6 +550,7 @@ abstract contract ERC4626ValidationTest is HyperdriveTest { (maturityTime, baseAmount) = hyperdrive.openShort( bondAmount, type(uint256).max, + 0, trader, asUnderlying ); diff --git a/test/integrations/hyperdrive/NegativeInterestLongFeeTest.t.sol b/test/integrations/hyperdrive/NegativeInterestLongFeeTest.t.sol index 3ba6e981f..aa0046f5f 100644 --- a/test/integrations/hyperdrive/NegativeInterestLongFeeTest.t.sol +++ b/test/integrations/hyperdrive/NegativeInterestLongFeeTest.t.sol @@ -139,6 +139,7 @@ contract NegativeInterestLongFeeTest is HyperdriveTest { DepositOverrides({ asUnderlying: true, depositAmount: basePaid, + minSharePrice: 0, minSlippage: 0, // TODO: This should never go below the base amount. Investigate this. maxSlippage: type(uint256).max }) @@ -331,6 +332,7 @@ contract NegativeInterestLongFeeTest is HyperdriveTest { DepositOverrides({ asUnderlying: true, depositAmount: basePaid, + minSharePrice: 0, minSlippage: 0, // TODO: This should never go below the base amount. Investigate this. maxSlippage: type(uint256).max }) @@ -522,6 +524,7 @@ contract NegativeInterestLongFeeTest is HyperdriveTest { DepositOverrides({ asUnderlying: true, depositAmount: basePaid, + minSharePrice: 0, minSlippage: 0, // TODO: This should never go below the base amount. Investigate this. maxSlippage: type(uint256).max }) diff --git a/test/integrations/hyperdrive/NegativeInterestShortFeeTest.t.sol b/test/integrations/hyperdrive/NegativeInterestShortFeeTest.t.sol index fc62ac8e5..80433ffbd 100644 --- a/test/integrations/hyperdrive/NegativeInterestShortFeeTest.t.sol +++ b/test/integrations/hyperdrive/NegativeInterestShortFeeTest.t.sol @@ -322,6 +322,7 @@ contract NegativeInterestShortFeeTest is HyperdriveTest { asUnderlying: true, // NOTE: Roughly double deposit amount needed to cover 100% flat fee depositAmount: shortAmount * 2, + minSharePrice: 0, minSlippage: 0, maxSlippage: type(uint256).max }) @@ -521,6 +522,7 @@ contract NegativeInterestShortFeeTest is HyperdriveTest { asUnderlying: true, // NOTE: Roughly double deposit amount needed to cover 100% flat fee depositAmount: shortAmount * 2, + minSharePrice: 0, minSlippage: 0, maxSlippage: type(uint256).max }) diff --git a/test/integrations/hyperdrive/ReentrancyTest.t.sol b/test/integrations/hyperdrive/ReentrancyTest.t.sol index f4c6bb044..351d882cd 100644 --- a/test/integrations/hyperdrive/ReentrancyTest.t.sol +++ b/test/integrations/hyperdrive/ReentrancyTest.t.sol @@ -133,7 +133,7 @@ contract ReentrancyTest is HyperdriveTest { ); data[4] = abi.encodeCall( hyperdrive.openLong, - (BASE_PAID, 0, _trader, true) + (BASE_PAID, 0, 0, _trader, true) ); data[5] = abi.encodeCall( hyperdrive.closeLong, @@ -141,7 +141,7 @@ contract ReentrancyTest is HyperdriveTest { ); data[6] = abi.encodeCall( hyperdrive.openShort, - (BOND_AMOUNT, type(uint256).max, _trader, true) + (BOND_AMOUNT, type(uint256).max, 0, _trader, true) ); data[7] = abi.encodeCall( hyperdrive.closeShort, @@ -187,6 +187,7 @@ contract ReentrancyTest is HyperdriveTest { DepositOverrides({ asUnderlying: true, depositAmount: CONTRIBUTION + 1, + minSharePrice: 0, minSlippage: 0, maxSlippage: type(uint256).max }) @@ -214,6 +215,7 @@ contract ReentrancyTest is HyperdriveTest { DepositOverrides({ asUnderlying: true, depositAmount: CONTRIBUTION + 1, + minSharePrice: 0, minSlippage: 0, maxSlippage: type(uint256).max }) @@ -279,6 +281,7 @@ contract ReentrancyTest is HyperdriveTest { DepositOverrides({ asUnderlying: true, depositAmount: BASE_PAID + 1, + minSharePrice: 0, minSlippage: 0, maxSlippage: type(uint256).max }) @@ -318,6 +321,7 @@ contract ReentrancyTest is HyperdriveTest { asUnderlying: true, // NOTE: Roughly double deposit amount needed to cover 100% flat fee depositAmount: BOND_AMOUNT * 2, + minSharePrice: 0, minSlippage: 0, maxSlippage: type(uint256).max }) @@ -335,6 +339,7 @@ contract ReentrancyTest is HyperdriveTest { asUnderlying: true, // NOTE: Roughly double deposit amount needed to cover 100% flat fee depositAmount: BOND_AMOUNT * 2, + minSharePrice: 0, minSlippage: 0, maxSlippage: type(uint256).max }) diff --git a/test/units/hyperdrive/CloseShortTest.t.sol b/test/units/hyperdrive/CloseShortTest.t.sol index b1178d5ae..31b084a67 100644 --- a/test/units/hyperdrive/CloseShortTest.t.sol +++ b/test/units/hyperdrive/CloseShortTest.t.sol @@ -472,6 +472,7 @@ contract CloseShortTest is HyperdriveTest { asUnderlying: false, // NOTE: Roughly double deposit amount needed to cover 100% flat fee depositAmount: 10e18 * 2, + minSharePrice: 0, minSlippage: 0, maxSlippage: type(uint128).max }) @@ -501,6 +502,7 @@ contract CloseShortTest is HyperdriveTest { asUnderlying: false, // NOTE: Roughly double deposit amount needed to cover 100% flat fee depositAmount: 10e18 * 2, + minSharePrice: 0, minSlippage: 0, maxSlippage: type(uint128).max }) @@ -539,6 +541,7 @@ contract CloseShortTest is HyperdriveTest { asUnderlying: false, // NOTE: Roughly double deposit amount needed to cover 100% flat fee depositAmount: 10e18 * 2, + minSharePrice: 0, minSlippage: 0, maxSlippage: type(uint128).max }) @@ -567,6 +570,7 @@ contract CloseShortTest is HyperdriveTest { asUnderlying: false, // NOTE: Roughly double deposit amount needed to cover 100% flat fee depositAmount: 10e18 * 2, + minSharePrice: 0, minSlippage: 0, maxSlippage: type(uint128).max }) diff --git a/test/units/hyperdrive/ExtremeInputs.t.sol b/test/units/hyperdrive/ExtremeInputs.t.sol index 0600d5ba1..86dba2404 100644 --- a/test/units/hyperdrive/ExtremeInputs.t.sol +++ b/test/units/hyperdrive/ExtremeInputs.t.sol @@ -214,7 +214,7 @@ contract ExtremeInputs is HyperdriveTest { baseToken.mint(shortAmount); baseToken.approve(address(hyperdrive), shortAmount); vm.expectRevert(IHyperdrive.BaseBufferExceedsShareReserves.selector); - hyperdrive.openShort(shortAmount, type(uint256).max, bob, true); + hyperdrive.openShort(shortAmount, type(uint256).max, 0, bob, true); } // This test stresses the edge cases of the `_updateLiquidity` function diff --git a/test/units/hyperdrive/FeeTest.t.sol b/test/units/hyperdrive/FeeTest.t.sol index 2ea4664b3..0984e8071 100644 --- a/test/units/hyperdrive/FeeTest.t.sol +++ b/test/units/hyperdrive/FeeTest.t.sol @@ -77,6 +77,7 @@ contract FeeTest is HyperdriveTest { DepositOverrides({ asUnderlying: true, depositAmount: basePaid, + minSharePrice: 0, minSlippage: 0, maxSlippage: type(uint256).max }) @@ -117,6 +118,7 @@ contract FeeTest is HyperdriveTest { DepositOverrides({ asUnderlying: true, depositAmount: basePaid, + minSharePrice: 0, minSlippage: 0, maxSlippage: type(uint256).max }) @@ -180,6 +182,7 @@ contract FeeTest is HyperdriveTest { DepositOverrides({ asUnderlying: true, depositAmount: basePaid, + minSharePrice: 0, minSlippage: 0, maxSlippage: type(uint256).max }) @@ -241,6 +244,7 @@ contract FeeTest is HyperdriveTest { DepositOverrides({ asUnderlying: true, depositAmount: basePaid, + minSharePrice: 0, minSlippage: 0, maxSlippage: type(uint256).max }) diff --git a/test/units/hyperdrive/OpenLongTest.t.sol b/test/units/hyperdrive/OpenLongTest.t.sol index 30515598a..b41f87bc3 100644 --- a/test/units/hyperdrive/OpenLongTest.t.sol +++ b/test/units/hyperdrive/OpenLongTest.t.sol @@ -37,7 +37,7 @@ contract OpenLongTest is HyperdriveTest { vm.stopPrank(); vm.startPrank(bob); vm.expectRevert(IHyperdrive.MinimumTransactionAmount.selector); - hyperdrive.openLong(0, 0, bob, true); + hyperdrive.openLong(0, 0, 0, bob, true); } function test_open_long_failure_not_payable() external { @@ -51,7 +51,7 @@ contract OpenLongTest is HyperdriveTest { vm.stopPrank(); vm.startPrank(bob); vm.expectRevert(IHyperdrive.NotPayable.selector); - hyperdrive.openLong{ value: 1 }(1, 0, bob, true); + hyperdrive.openLong{ value: 1 }(1, 0, 0, bob, true); } function test_open_long_failure_pause() external { @@ -66,7 +66,7 @@ contract OpenLongTest is HyperdriveTest { pause(true); vm.startPrank(bob); vm.expectRevert(IHyperdrive.Paused.selector); - hyperdrive.openLong(0, 0, bob, true); + hyperdrive.openLong(0, 0, 0, bob, true); vm.stopPrank(); pause(false); } @@ -90,7 +90,7 @@ contract OpenLongTest is HyperdriveTest { baseToken.mint(bob, basePaid); baseToken.approve(address(hyperdrive), basePaid); vm.expectRevert(IHyperdrive.NegativeInterest.selector); - hyperdrive.openLong(basePaid, 0, bob, true); + hyperdrive.openLong(basePaid, 0, 0, bob, true); // Ensure that the max long results in spot price very close to 1 to // make sure that the negative interest failure was appropriate. @@ -123,7 +123,31 @@ contract OpenLongTest is HyperdriveTest { baseToken.mint(baseAmount); baseToken.approve(address(hyperdrive), baseAmount); vm.expectRevert(IHyperdrive.NegativeInterest.selector); - hyperdrive.openLong(baseAmount, 0, bob, true); + hyperdrive.openLong(baseAmount, 0, 0, bob, true); + } + + function test_open_long_failure_minimum_share_price() external { + uint256 apr = 0.05e18; + + // Initialize the pool with a large amount of capital. + uint256 contribution = 500_000_000e18; + initialize(alice, apr, contribution); + + // Attempt to open a long when the share price is lower than the minimum + // share price. + vm.stopPrank(); + vm.startPrank(bob); + uint256 baseAmount = 10e18; + baseToken.mint(baseAmount); + baseToken.approve(address(hyperdrive), baseAmount); + vm.expectRevert(IHyperdrive.MinimumSharePrice.selector); + hyperdrive.openLong( + baseAmount, + 0, + 2 * hyperdrive.getPoolInfo().sharePrice, + bob, + true + ); } function test_open_long() external { @@ -196,7 +220,7 @@ contract OpenLongTest is HyperdriveTest { baseToken.approve(address(hyperdrive), longAmount); vm.expectRevert(IHyperdrive.BaseBufferExceedsShareReserves.selector); - hyperdrive.openLong(longAmount, 0, bob, true); + hyperdrive.openLong(longAmount, 0, 0, bob, true); } function testAvoidsDustAttack(uint256 contribution, uint256 apr) public { diff --git a/test/units/hyperdrive/OpenShortTest.t.sol b/test/units/hyperdrive/OpenShortTest.t.sol index b9f230540..7a29b7366 100644 --- a/test/units/hyperdrive/OpenShortTest.t.sol +++ b/test/units/hyperdrive/OpenShortTest.t.sol @@ -33,7 +33,7 @@ contract OpenShortTest is HyperdriveTest { vm.stopPrank(); vm.startPrank(bob); vm.expectRevert(IHyperdrive.MinimumTransactionAmount.selector); - hyperdrive.openShort(0, type(uint256).max, bob, true); + hyperdrive.openShort(0, type(uint256).max, 0, bob, true); } function test_open_short_failure_not_payable() external { @@ -47,7 +47,7 @@ contract OpenShortTest is HyperdriveTest { vm.stopPrank(); vm.startPrank(bob); vm.expectRevert(IHyperdrive.NotPayable.selector); - hyperdrive.openShort{ value: 1 }(1, type(uint256).max, bob, true); + hyperdrive.openShort{ value: 1 }(1, type(uint256).max, 0, bob, true); } function test_open_short_failure_pause() external { @@ -62,7 +62,7 @@ contract OpenShortTest is HyperdriveTest { pause(true); vm.startPrank(bob); vm.expectRevert(IHyperdrive.Paused.selector); - hyperdrive.openShort(0, type(uint256).max, bob, true); + hyperdrive.openShort(0, type(uint256).max, 0, bob, true); vm.stopPrank(); pause(false); } @@ -81,7 +81,7 @@ contract OpenShortTest is HyperdriveTest { baseToken.mint(shortAmount); baseToken.approve(address(hyperdrive), shortAmount); vm.expectRevert(IHyperdrive.InvalidTradeSize.selector); - hyperdrive.openShort(shortAmount * 2, type(uint256).max, bob, true); + hyperdrive.openShort(shortAmount * 2, type(uint256).max, 0, bob, true); } function test_open_short() external { @@ -192,6 +192,7 @@ contract OpenShortTest is HyperdriveTest { DepositOverrides memory depositOverrides = DepositOverrides({ asUnderlying: false, depositAmount: bondAmount * 2, + minSharePrice: 0, minSlippage: 0, maxSlippage: type(uint128).max }); diff --git a/test/units/libraries/HyperdriveMath.t.sol b/test/units/libraries/HyperdriveMath.t.sol index dc104c65a..bed40802a 100644 --- a/test/units/libraries/HyperdriveMath.t.sol +++ b/test/units/libraries/HyperdriveMath.t.sol @@ -633,7 +633,7 @@ contract HyperdriveMathTest is HyperdriveTest { baseToken.mint(bob, finalLongAmount); baseToken.approve(address(hyperdrive), finalLongAmount); vm.expectRevert(); - hyperdrive.openLong(finalLongAmount, 0, bob, true); + hyperdrive.openLong(finalLongAmount, 0, 0, bob, true); // Ensure that the long can be closed. closeLong(bob, maturityTime, longAmount); @@ -795,7 +795,7 @@ contract HyperdriveMathTest is HyperdriveTest { baseToken.mint(bob, finalShortAmount); baseToken.approve(address(hyperdrive), finalShortAmount); vm.expectRevert(); - hyperdrive.openShort(finalShortAmount, 0, bob, true); + hyperdrive.openShort(finalShortAmount, 0, 0, bob, true); // Ensure that the short can be closed. closeShort(bob, maturityTime, maxShort); diff --git a/test/utils/HyperdriveTest.sol b/test/utils/HyperdriveTest.sol index 2348d3bd4..b5ad14ef1 100644 --- a/test/utils/HyperdriveTest.sol +++ b/test/utils/HyperdriveTest.sol @@ -175,6 +175,9 @@ contract HyperdriveTest is BaseTest { // transferred into the YieldSource, which allows us to test ETH // reentrancy. uint256 depositAmount; + // The minimum share price that will be accepted. It may not be used by + // some actions. + uint256 minSharePrice; // This is the slippage parameter that defines a lower bound on the // quantity being measured. It may not be used by some actions. uint256 minSlippage; @@ -239,6 +242,7 @@ contract HyperdriveTest is BaseTest { DepositOverrides({ asUnderlying: true, depositAmount: contribution, + minSharePrice: 0, // unused minSlippage: 0, // unused maxSlippage: type(uint256).max // unused }) @@ -259,6 +263,7 @@ contract HyperdriveTest is BaseTest { DepositOverrides({ asUnderlying: asUnderlying, depositAmount: contribution, + minSharePrice: 0, // unused minSlippage: 0, // unused maxSlippage: type(uint256).max // unused }) @@ -311,6 +316,7 @@ contract HyperdriveTest is BaseTest { DepositOverrides({ asUnderlying: true, depositAmount: contribution, + minSharePrice: 0, // unused minSlippage: 0, // min spot rate of 0 maxSlippage: type(uint256).max // max spot rate of uint256 max }) @@ -329,6 +335,7 @@ contract HyperdriveTest is BaseTest { DepositOverrides({ asUnderlying: asUnderlying, depositAmount: contribution, + minSharePrice: 0, // unused minSlippage: 0, // min spot rate of 0 maxSlippage: type(uint256).max // max spot rate of uint256 max }) @@ -450,6 +457,7 @@ contract HyperdriveTest is BaseTest { hyperdrive.openLong{ value: overrides.depositAmount }( baseAmount, overrides.minSlippage, // min bond proceeds + overrides.minSharePrice, trader, overrides.asUnderlying ); @@ -460,6 +468,7 @@ contract HyperdriveTest is BaseTest { hyperdrive.openLong( baseAmount, overrides.minSlippage, // min bond proceeds + overrides.minSharePrice, trader, overrides.asUnderlying ); @@ -477,6 +486,7 @@ contract HyperdriveTest is BaseTest { DepositOverrides({ asUnderlying: true, depositAmount: baseAmount, + minSharePrice: 0, // min share price of 0 minSlippage: baseAmount, // min bond proceeds of baseAmount maxSlippage: type(uint256).max // unused }) @@ -495,6 +505,7 @@ contract HyperdriveTest is BaseTest { DepositOverrides({ asUnderlying: asUnderlying, depositAmount: baseAmount, + minSharePrice: 0, // min share price of 0 minSlippage: baseAmount, // min bond proceeds of baseAmount maxSlippage: type(uint256).max // unused }) @@ -577,6 +588,7 @@ contract HyperdriveTest is BaseTest { }( bondAmount, overrides.maxSlippage, // max base payment + overrides.minSharePrice, trader, overrides.asUnderlying ); @@ -586,6 +598,7 @@ contract HyperdriveTest is BaseTest { (maturityTime, baseAmount) = hyperdrive.openShort( bondAmount, overrides.maxSlippage, // max base payment + overrides.minSharePrice, trader, overrides.asUnderlying ); @@ -606,6 +619,7 @@ contract HyperdriveTest is BaseTest { DepositOverrides({ asUnderlying: true, depositAmount: bondAmount, + minSharePrice: 0, // min share price of 0 minSlippage: 0, // unused maxSlippage: bondAmount // max base payment of bondAmount }) @@ -624,6 +638,7 @@ contract HyperdriveTest is BaseTest { DepositOverrides({ asUnderlying: asUnderlying, depositAmount: bondAmount, + minSharePrice: 0, // min share price of 0 minSlippage: 0, // unused maxSlippage: bondAmount // max base payment of bondAmount }) From 0306a2b2d0e2206ff61ce3297686f7e2feb1bc23 Mon Sep 17 00:00:00 2001 From: Alex Towle Date: Thu, 28 Sep 2023 17:37:14 -0500 Subject: [PATCH 2/3] Fixed some tests --- .../hyperdrive/RoundTripTest.t.sol | 35 ++++++++++++++----- test/units/hyperdrive/OpenLongTest.t.sol | 9 ++--- test/units/hyperdrive/OpenShortTest.t.sol | 25 +++++++++++++ 3 files changed, 54 insertions(+), 15 deletions(-) diff --git a/test/integrations/hyperdrive/RoundTripTest.t.sol b/test/integrations/hyperdrive/RoundTripTest.t.sol index f8733e152..f71335140 100644 --- a/test/integrations/hyperdrive/RoundTripTest.t.sol +++ b/test/integrations/hyperdrive/RoundTripTest.t.sol @@ -181,14 +181,33 @@ contract RoundTripTest is HyperdriveTest { function test_long_multiblock_round_trip_end_of_checkpoint_edge_cases() external { - uint256 apr = 115792089237316195423570985008687907853269984665640564039457583990320674062335; - uint256 timeStretchApr = 886936259672610464646559504023817532562726574141720139630650341263; - uint256 basePaid = 65723876150308947051900890891865009457038319412461; - _test_long_multiblock_round_trip_end_of_checkpoint( - apr, - timeStretchApr, - basePaid - ); + uint256 snapshotId = vm.snapshot(); + { + uint256 apr = 115792089237316195423570985008687907853269984665640564039457583990320674062335; + uint256 timeStretchApr = 886936259672610464646559504023817532562726574141720139630650341263; + uint256 basePaid = 65723876150308947051900890891865009457038319412461; + _test_long_multiblock_round_trip_end_of_checkpoint( + apr, + timeStretchApr, + basePaid + ); + } + vm.revertTo(snapshotId); + + // TODO: This test fails because the calculateMaxLong seems to be misbehaving. + // See issue #595 + // snapshotId = vm.snapshot(); + // { + // uint256 apr = 63203229717248733662763783222570; + // uint256 timeStretchApr = 3408059979187494427077136; + // uint256 basePaid = 57669888194155013968076316270639259357724635816572534634741412969387347636732; + // _test_long_multiblock_round_trip_end_of_checkpoint( + // apr, + // timeStretchApr, + // basePaid + // ); + // } + // vm.revertTo(snapshotId); // TODO: This test fails because the calculateMaxLong seems to be misbehaving. // See issue #595 diff --git a/test/units/hyperdrive/OpenLongTest.t.sol b/test/units/hyperdrive/OpenLongTest.t.sol index b41f87bc3..10aefa20f 100644 --- a/test/units/hyperdrive/OpenLongTest.t.sol +++ b/test/units/hyperdrive/OpenLongTest.t.sol @@ -140,14 +140,9 @@ contract OpenLongTest is HyperdriveTest { uint256 baseAmount = 10e18; baseToken.mint(baseAmount); baseToken.approve(address(hyperdrive), baseAmount); + uint256 minSharePrice = 2 * hyperdrive.getPoolInfo().sharePrice; vm.expectRevert(IHyperdrive.MinimumSharePrice.selector); - hyperdrive.openLong( - baseAmount, - 0, - 2 * hyperdrive.getPoolInfo().sharePrice, - bob, - true - ); + hyperdrive.openLong(baseAmount, 0, minSharePrice, bob, true); } function test_open_long() external { diff --git a/test/units/hyperdrive/OpenShortTest.t.sol b/test/units/hyperdrive/OpenShortTest.t.sol index 7a29b7366..6272ffec8 100644 --- a/test/units/hyperdrive/OpenShortTest.t.sol +++ b/test/units/hyperdrive/OpenShortTest.t.sol @@ -84,6 +84,31 @@ contract OpenShortTest is HyperdriveTest { hyperdrive.openShort(shortAmount * 2, type(uint256).max, 0, bob, true); } + function test_open_short_failure_minimum_share_price() external { + uint256 apr = 0.05e18; + + // Initialize the pool with a large amount of capital. + uint256 contribution = 500_000_000e18; + initialize(alice, apr, contribution); + + // Attempt to open a long when the share price is lower than the minimum + // share price. + vm.stopPrank(); + vm.startPrank(bob); + uint256 bondAmount = 10e18; + baseToken.mint(bondAmount); + baseToken.approve(address(hyperdrive), bondAmount); + uint256 minSharePrice = 2 * hyperdrive.getPoolInfo().sharePrice; + vm.expectRevert(IHyperdrive.MinimumSharePrice.selector); + hyperdrive.openShort( + bondAmount, + type(uint256).max, + minSharePrice, + bob, + true + ); + } + function test_open_short() external { uint256 apr = 0.05e18; From 6730117aeda191b1b434bc6f76ffb75ed60735ac Mon Sep 17 00:00:00 2001 From: Alex Towle Date: Fri, 29 Sep 2023 11:34:55 -0500 Subject: [PATCH 3/3] Fixed the Rust codebase --- crates/test-utils/src/agent.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/crates/test-utils/src/agent.rs b/crates/test-utils/src/agent.rs index 51199400a..ec357e763 100644 --- a/crates/test-utils/src/agent.rs +++ b/crates/test-utils/src/agent.rs @@ -134,7 +134,13 @@ impl Agent { }; let tx = self .hyperdrive - .open_long(base_paid.into(), min_output.into(), self.address, true) + .open_long( + base_paid.into(), + min_output.into(), + fixed!(0).into(), // TODO: This is fine for testing, but not prod. + self.address, + true, + ) .from(self.address); let logs = tx .send() @@ -241,7 +247,13 @@ impl Agent { }; let tx = self .hyperdrive - .open_short(bond_amount.into(), max_deposit.into(), self.address, true) + .open_short( + bond_amount.into(), + max_deposit.into(), + fixed!(0).into(), // TODO: This is fine for testing, but not prod. + self.address, + true, + ) .from(self.address); let logs = tx .send()