From 0cfc1e9309f2b866356444517df9c8a875a9990e Mon Sep 17 00:00:00 2001 From: Xiangyu Xu Date: Thu, 20 Mar 2025 12:31:25 -0700 Subject: [PATCH 1/4] fix: make _openLong use try-catch to prevent tend() from reverting when hitting deposit caps --- contracts/EverlongStrategy.sol | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/contracts/EverlongStrategy.sol b/contracts/EverlongStrategy.sol index 70c25cb..269e006 100644 --- a/contracts/EverlongStrategy.sol +++ b/contracts/EverlongStrategy.sol @@ -344,8 +344,11 @@ contract EverlongStrategy is BaseStrategy { tendConfig.extraData ); - // Account for the new position in the portfolio. - _portfolio.handleOpenPosition(maturityTime, bondAmount); + // Only update the portfolio if _openLong was successful (indicated by non-zero bondAmount) + if (bondAmount > 0) { + // Account for the new position in the portfolio. + _portfolio.handleOpenPosition(maturityTime, bondAmount); + } } } @@ -708,14 +711,24 @@ contract EverlongStrategy is BaseStrategy { ERC20(asset).forceApprove(address(hyperdrive), _toSpend + 1); } - // Open the long. Return the maturity time and amount of bonds received. - (maturityTime, bondAmount) = IHyperdrive(hyperdrive).openLong( + // Open the long using a try-catch to handle potential failures + // (e.g., deposit caps) gracefully. + try IHyperdrive(hyperdrive).openLong( asBase, _toSpend, _minOutput, _minVaultSharePrice, _extraData - ); + ) returns (uint256 _maturityTime, uint256 _bondAmount) { + maturityTime = _maturityTime; + bondAmount = _bondAmount; + } catch { + // If the openLong fails (e.g., due to deposit caps), return zeros + // to indicate no position was opened. The calling function (_tend) + // will not update the portfolio in this case. + maturityTime = 0; + bondAmount = 0; + } } /// @dev Preview the amount of assets received from closing the specified From 7216413756c192b7d02c4d7828963e7b18029aba Mon Sep 17 00:00:00 2001 From: Xiangyu Xu Date: Thu, 20 Mar 2025 14:17:04 -0700 Subject: [PATCH 2/4] Bug fix: call external IHyperdrive directly instead of the wrapper in HyperdriveExecution lib which is an internal call and blocks try/catch --- contracts/EverlongStrategy.sol | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/contracts/EverlongStrategy.sol b/contracts/EverlongStrategy.sol index 269e006..a654eb6 100644 --- a/contracts/EverlongStrategy.sol +++ b/contracts/EverlongStrategy.sol @@ -713,13 +713,18 @@ contract EverlongStrategy is BaseStrategy { // Open the long using a try-catch to handle potential failures // (e.g., deposit caps) gracefully. - try IHyperdrive(hyperdrive).openLong( - asBase, - _toSpend, - _minOutput, - _minVaultSharePrice, - _extraData - ) returns (uint256 _maturityTime, uint256 _bondAmount) { + try + IHyperdrive(hyperdrive).openLong( + _toSpend, + _minOutput, + _minVaultSharePrice, + IHyperdrive.Options({ + destination: address(this), + asBase: asBase, + extraData: _extraData + }) + ) + returns (uint256 _maturityTime, uint256 _bondAmount) { maturityTime = _maturityTime; bondAmount = _bondAmount; } catch { From ac08a0d35e8d41f91bff292a9e8712c84c9887b7 Mon Sep 17 00:00:00 2001 From: Xiangyu Xu Date: Thu, 20 Mar 2025 14:21:10 -0700 Subject: [PATCH 3/4] Manually add the event emission --- contracts/EverlongStrategy.sol | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/contracts/EverlongStrategy.sol b/contracts/EverlongStrategy.sol index a654eb6..7d47457 100644 --- a/contracts/EverlongStrategy.sol +++ b/contracts/EverlongStrategy.sol @@ -8,9 +8,10 @@ import { SafeERC20 } from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; import { BaseStrategy, ERC20 } from "tokenized-strategy/BaseStrategy.sol"; import { IEverlongStrategy } from "./interfaces/IEverlongStrategy.sol"; import { IERC20Wrappable } from "./interfaces/IERC20Wrappable.sol"; -import { EVERLONG_STRATEGY_KIND, EVERLONG_VERSION, MAX_BPS, ONE } from "./libraries/Constants.sol"; +import { EVERLONG_STRATEGY_KIND, EVERLONG_VERSION, ONE } from "./libraries/Constants.sol"; import { EverlongPortfolioLibrary } from "./libraries/EverlongPortfolio.sol"; import { HyperdriveExecutionLibrary } from "./libraries/HyperdriveExecution.sol"; +import { IEverlongEvents } from "./interfaces/IEverlongEvents.sol"; // ,---..-. .-.,---. ,---. ,-. .---. .-. .-. ,--, // | .-' \ \ / / | .-' | .-.\ | | / .-. ) | \| |.' .' @@ -727,6 +728,12 @@ contract EverlongStrategy is BaseStrategy { returns (uint256 _maturityTime, uint256 _bondAmount) { maturityTime = _maturityTime; bondAmount = _bondAmount; + + // Emit the position opened event as the library function would + emit IEverlongEvents.PositionOpened( + maturityTime.toUint128(), + bondAmount.toUint128() + ); } catch { // If the openLong fails (e.g., due to deposit caps), return zeros // to indicate no position was opened. The calling function (_tend) From d3a0c105afe3ccf149c83dff435cf34b0b3e15d4 Mon Sep 17 00:00:00 2001 From: Xiangyu Xu Date: Thu, 20 Mar 2025 14:30:29 -0700 Subject: [PATCH 4/4] Update test cases according to the new try/catch code --- test/everlong/units/Tend.t.sol | 38 ++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/test/everlong/units/Tend.t.sol b/test/everlong/units/Tend.t.sol index b23576a..3f43dc1 100644 --- a/test/everlong/units/Tend.t.sol +++ b/test/everlong/units/Tend.t.sol @@ -227,8 +227,11 @@ contract TestTend is EverlongTest { // Set minOutput to a very high value. uint256 minOutput = type(uint256).max; - // Ensure `tend()` reverts. - vm.expectRevert(); + // Record the initial position count and portfolio state + uint256 initialPositionCount = IEverlongStrategy(address(strategy)).positionCount(); + uint256 initialTotalBonds = IEverlongStrategy(address(strategy)).totalBonds(); + + // Call tend() with extreme minOutput - should not revert with try/catch in place keeperContract.tend( address(strategy), IEverlongStrategy.TendConfig({ @@ -239,6 +242,18 @@ contract TestTend is EverlongTest { }) ); + // Verify that no new position was opened (portfolio was not updated) + assertEq( + IEverlongStrategy(address(strategy)).positionCount(), + initialPositionCount, + "Position count should not change with impossible minOutput" + ); + assertEq( + IEverlongStrategy(address(strategy)).totalBonds(), + initialTotalBonds, + "Total bonds should not change with impossible minOutput" + ); + // Stop the prank. vm.stopPrank(); } @@ -257,8 +272,11 @@ contract TestTend is EverlongTest { // Set minVaultSharePrice to a very high value. uint256 minVaultSharePrice = type(uint256).max; - // Ensure `tend()` reverts. - vm.expectRevert(); + // Record the initial position count and portfolio state + uint256 initialPositionCount = IEverlongStrategy(address(strategy)).positionCount(); + uint256 initialTotalBonds = IEverlongStrategy(address(strategy)).totalBonds(); + + // Call tend() with extreme minVaultSharePrice - should not revert with try/catch in place keeperContract.tend( address(strategy), IEverlongStrategy.TendConfig({ @@ -269,6 +287,18 @@ contract TestTend is EverlongTest { }) ); + // Verify that no new position was opened (portfolio was not updated) + assertEq( + IEverlongStrategy(address(strategy)).positionCount(), + initialPositionCount, + "Position count should not change with impossible minVaultSharePrice" + ); + assertEq( + IEverlongStrategy(address(strategy)).totalBonds(), + initialTotalBonds, + "Total bonds should not change with impossible minVaultSharePrice" + ); + // Stop the prank. vm.stopPrank(); }