Skip to content

Commit 44f2049

Browse files
authored
Fix: tend() can fail if Hyperdrive vault has deposit caps #18 (#51)
* fix: make _openLong use try-catch to prevent tend() from reverting when hitting deposit caps * Bug fix: call external IHyperdrive directly instead of the wrapper in HyperdriveExecution lib which is an internal call and blocks try/catch * Manually add the event emission * Update test cases according to the new try/catch code
1 parent 356f7ce commit 44f2049

2 files changed

Lines changed: 70 additions & 15 deletions

File tree

contracts/EverlongStrategy.sol

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import { SafeERC20 } from "openzeppelin/token/ERC20/utils/SafeERC20.sol";
88
import { BaseStrategy, ERC20 } from "tokenized-strategy/BaseStrategy.sol";
99
import { IEverlongStrategy } from "./interfaces/IEverlongStrategy.sol";
1010
import { IERC20Wrappable } from "./interfaces/IERC20Wrappable.sol";
11-
import { EVERLONG_STRATEGY_KIND, EVERLONG_VERSION, MAX_BPS, ONE } from "./libraries/Constants.sol";
11+
import { EVERLONG_STRATEGY_KIND, EVERLONG_VERSION, ONE } from "./libraries/Constants.sol";
1212
import { EverlongPortfolioLibrary } from "./libraries/EverlongPortfolio.sol";
1313
import { HyperdriveExecutionLibrary } from "./libraries/HyperdriveExecution.sol";
14+
import { IEverlongEvents } from "./interfaces/IEverlongEvents.sol";
1415

1516
// ,---..-. .-.,---. ,---. ,-. .---. .-. .-. ,--,
1617
// | .-' \ \ / / | .-' | .-.\ | | / .-. ) | \| |.' .'
@@ -344,8 +345,11 @@ contract EverlongStrategy is BaseStrategy {
344345
tendConfig.extraData
345346
);
346347

347-
// Account for the new position in the portfolio.
348-
_portfolio.handleOpenPosition(maturityTime, bondAmount);
348+
// Only update the portfolio if _openLong was successful (indicated by non-zero bondAmount)
349+
if (bondAmount > 0) {
350+
// Account for the new position in the portfolio.
351+
_portfolio.handleOpenPosition(maturityTime, bondAmount);
352+
}
349353
}
350354
}
351355

@@ -708,14 +712,35 @@ contract EverlongStrategy is BaseStrategy {
708712
ERC20(asset).forceApprove(address(hyperdrive), _toSpend + 1);
709713
}
710714

711-
// Open the long. Return the maturity time and amount of bonds received.
712-
(maturityTime, bondAmount) = IHyperdrive(hyperdrive).openLong(
713-
asBase,
714-
_toSpend,
715-
_minOutput,
716-
_minVaultSharePrice,
717-
_extraData
718-
);
715+
// Open the long using a try-catch to handle potential failures
716+
// (e.g., deposit caps) gracefully.
717+
try
718+
IHyperdrive(hyperdrive).openLong(
719+
_toSpend,
720+
_minOutput,
721+
_minVaultSharePrice,
722+
IHyperdrive.Options({
723+
destination: address(this),
724+
asBase: asBase,
725+
extraData: _extraData
726+
})
727+
)
728+
returns (uint256 _maturityTime, uint256 _bondAmount) {
729+
maturityTime = _maturityTime;
730+
bondAmount = _bondAmount;
731+
732+
// Emit the position opened event as the library function would
733+
emit IEverlongEvents.PositionOpened(
734+
maturityTime.toUint128(),
735+
bondAmount.toUint128()
736+
);
737+
} catch {
738+
// If the openLong fails (e.g., due to deposit caps), return zeros
739+
// to indicate no position was opened. The calling function (_tend)
740+
// will not update the portfolio in this case.
741+
maturityTime = 0;
742+
bondAmount = 0;
743+
}
719744
}
720745

721746
/// @dev Preview the amount of assets received from closing the specified

test/everlong/units/Tend.t.sol

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,11 @@ contract TestTend is EverlongTest {
227227
// Set minOutput to a very high value.
228228
uint256 minOutput = type(uint256).max;
229229

230-
// Ensure `tend()` reverts.
231-
vm.expectRevert();
230+
// Record the initial position count and portfolio state
231+
uint256 initialPositionCount = IEverlongStrategy(address(strategy)).positionCount();
232+
uint256 initialTotalBonds = IEverlongStrategy(address(strategy)).totalBonds();
233+
234+
// Call tend() with extreme minOutput - should not revert with try/catch in place
232235
keeperContract.tend(
233236
address(strategy),
234237
IEverlongStrategy.TendConfig({
@@ -239,6 +242,18 @@ contract TestTend is EverlongTest {
239242
})
240243
);
241244

245+
// Verify that no new position was opened (portfolio was not updated)
246+
assertEq(
247+
IEverlongStrategy(address(strategy)).positionCount(),
248+
initialPositionCount,
249+
"Position count should not change with impossible minOutput"
250+
);
251+
assertEq(
252+
IEverlongStrategy(address(strategy)).totalBonds(),
253+
initialTotalBonds,
254+
"Total bonds should not change with impossible minOutput"
255+
);
256+
242257
// Stop the prank.
243258
vm.stopPrank();
244259
}
@@ -257,8 +272,11 @@ contract TestTend is EverlongTest {
257272
// Set minVaultSharePrice to a very high value.
258273
uint256 minVaultSharePrice = type(uint256).max;
259274

260-
// Ensure `tend()` reverts.
261-
vm.expectRevert();
275+
// Record the initial position count and portfolio state
276+
uint256 initialPositionCount = IEverlongStrategy(address(strategy)).positionCount();
277+
uint256 initialTotalBonds = IEverlongStrategy(address(strategy)).totalBonds();
278+
279+
// Call tend() with extreme minVaultSharePrice - should not revert with try/catch in place
262280
keeperContract.tend(
263281
address(strategy),
264282
IEverlongStrategy.TendConfig({
@@ -269,6 +287,18 @@ contract TestTend is EverlongTest {
269287
})
270288
);
271289

290+
// Verify that no new position was opened (portfolio was not updated)
291+
assertEq(
292+
IEverlongStrategy(address(strategy)).positionCount(),
293+
initialPositionCount,
294+
"Position count should not change with impossible minVaultSharePrice"
295+
);
296+
assertEq(
297+
IEverlongStrategy(address(strategy)).totalBonds(),
298+
initialTotalBonds,
299+
"Total bonds should not change with impossible minVaultSharePrice"
300+
);
301+
272302
// Stop the prank.
273303
vm.stopPrank();
274304
}

0 commit comments

Comments
 (0)