Skip to content

Commit d6d7b6e

Browse files
committed
Looped exit works
1 parent c0b40fb commit d6d7b6e

File tree

3 files changed

+63
-20
lines changed

3 files changed

+63
-20
lines changed

src/strategy/MorphoLoopStrategy.sol

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -472,17 +472,16 @@ contract MorphoLoopStrategy is
472472
console.log("=== onMorphoRepay callback ===");
473473
console.log("Repaid assets (WETH needed):", repaidAssets);
474474

475-
// 1. Validate caller is Morpho
476-
if (msg.sender != address(MORPHO)) revert UnauthorizedCallback();
477-
478-
// 2. Validate callback context
475+
// 1. Validate callback context
479476
CallbackContext memory ctx = _callbackContext;
480477
if (ctx.callBackFrom == address(0)) revert NoActiveContext();
481478
if (ctx.callbackType != MorphoCallback.OnMorphoRepay) revert InvalidMorphoCallback();
482479

480+
// 2. Validate caller is the callForwarder (callback is forwarded from forwarder)
481+
if (msg.sender != ctx.callBackFrom) revert UnauthorizedCallback();
482+
483483
// 3. Decode callback data
484484
RepayCallbackData memory cbData = abi.decode(data, (RepayCallbackData));
485-
IStrategyCallForwarder callForwarder = IStrategyCallForwarder(cbData.callForwarder);
486485

487486
// 4. Get current collateral amount and withdraw ALL from Morpho
488487
Position memory pos = MORPHO.position(MARKET_ID, cbData.callForwarder);
@@ -526,11 +525,18 @@ contract MorphoLoopStrategy is
526525
console.log("Transferred remaining wstETH to callForwarder");
527526
}
528527

529-
// 9. Approve WETH to Morpho for repayment
530-
WETH.approve(address(MORPHO), repaidAssets);
531-
console.log("Approved WETH to Morpho for repayment");
528+
// 9. Transfer WETH to callForwarder (Morpho pulls from msg.sender which is forwarder)
529+
WETH.transfer(cbData.callForwarder, repaidAssets);
530+
console.log("Transferred WETH to callForwarder for Morpho repayment");
531+
532+
// 10. Approve WETH from callForwarder to Morpho
533+
IStrategyCallForwarder(cbData.callForwarder).doCall(
534+
address(WETH),
535+
abi.encodeWithSelector(WETH.approve.selector, address(MORPHO), repaidAssets)
536+
);
537+
console.log("Approved WETH to Morpho from callForwarder");
532538

533-
// Morpho will pull the WETH after this callback returns
539+
// Morpho will pull the WETH from callForwarder after this callback returns
534540
console.log("=== onMorphoRepay callback complete ===");
535541
}
536542

@@ -581,8 +587,9 @@ contract MorphoLoopStrategy is
581587

582588
// 4. Execute repay with callback (repays ALL debt using shares)
583589
// The callback will withdraw collateral, swap to WETH, and approve for repayment
590+
// NOTE: We call via callForwarder so Morpho calls back to forwarder, which forwards to strategy
584591
if (pos.borrowShares > 0) {
585-
console.log("Calling Morpho.repay with callback");
592+
console.log("Calling Morpho.repay with callback via callForwarder");
586593
callForwarder.doCall(
587594
address(MORPHO),
588595
abi.encodeCall(

src/strategy/StrategyCallForwarder.sol

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
// SPDX-License-Identifier: GPL-3.0
1+
// SPDX-License-Identifier: MIT
22
pragma solidity 0.8.30;
33

44
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
55
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
66
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
77
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
88
import {
9+
IMorphoRepayCallback,
910
IMorphoSupplyCallback,
1011
IMorphoSupplyCollateralCallback
1112
} from "lib/morpho-blue/src/interfaces/IMorphoCallbacks.sol";
@@ -16,7 +17,8 @@ contract StrategyCallForwarder is
1617
ReentrancyGuardUpgradeable,
1718
OwnableUpgradeable,
1819
IStrategyCallForwarder,
19-
IMorphoSupplyCollateralCallback
20+
IMorphoSupplyCollateralCallback,
21+
IMorphoRepayCallback
2022
{
2123
constructor() {
2224
_disableInitializers();
@@ -76,4 +78,15 @@ contract StrategyCallForwarder is
7678
// Forward the callback to the owner (strategy)
7779
Address.functionCall(owner(), callData);
7880
}
81+
82+
function onMorphoRepay(uint256 assets, bytes calldata data) external {
83+
// TODO: Add some form of checking.
84+
// if (msg.sender != address(MORPHO)) revert UnauthorizedCallback();
85+
86+
// Forward the callback to the owner (strategy)
87+
bytes memory callData = abi.encodeCall(IMorphoRepayCallback.onMorphoRepay, (assets, data));
88+
89+
// Forward the callback to the owner (strategy)
90+
Address.functionCall(owner(), callData);
91+
}
7992
}

test/integration/morpho-loop.test.sol

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,8 @@ contract MorphoLoopStrategyTest is StvStrategyPoolHarness {
508508
assertGt(posBefore.collateral, 0, "Should have collateral");
509509
assertGt(posBefore.borrowShares, 0, "Should have debt");
510510

511+
// Check Lido position before
512+
511513
// 3. Execute exit with 1% slippage tolerance
512514
MorphoLoopStrategy.LoopExitParams memory exitParams = MorphoLoopStrategy.LoopExitParams({
513515
slippageBps: 100 // 1%
@@ -516,7 +518,9 @@ contract MorphoLoopStrategyTest is StvStrategyPoolHarness {
516518
vm.prank(USER1);
517519
bytes32 requestId = morphoStrategy.requestExitByWsteth(0, abi.encode(exitParams));
518520

519-
// 4. Verify position is closed
521+
// Check Lido position after.
522+
523+
// 4. Verify Morpho position is closed
520524
Position memory posAfter = morpho.position(marketId, user1StrategyCallForwarder);
521525
console.log("Position after exit:");
522526
console.log(" Collateral:", posAfter.collateral);
@@ -527,16 +531,37 @@ contract MorphoLoopStrategyTest is StvStrategyPoolHarness {
527531

528532
// 5. Verify withdrawal request created
529533
console.log("Withdrawal request ID:", uint256(requestId));
534+
assertEq(uint256(requestId), 1, "withdraw request ID should be 1");
530535
// Note: requestId will be bytes32(0) if pool withdrawal wasn't needed
536+
537+
// Get the call forwarder address for USER1.
538+
IStrategyCallForwarder callForwarder = morphoStrategy.getStrategyCallForwarderAddress(USER1);
539+
540+
// Check that we have a pending withdrawal request in the withdrawal queue.
541+
uint256[] memory withdrawals = withdrawalQueue.withdrawalRequestsOf(USER1);
542+
assertEq(withdrawals.length, 1, "withdrawal queue should have 1 withdrawal request");
543+
assertEq(withdrawals[0], 1, "the withdrawal should have ID 1");
544+
545+
// Withdrawal queue data
546+
WithdrawalQueue.WithdrawalRequestStatus memory withdrawalStatus = withdrawalQueue.getWithdrawalStatus(
547+
withdrawals[0]
548+
);
549+
console.log("amountOfStv: ", withdrawalStatus.amountOfStv);
550+
console.log("amountOfStethShares: ", withdrawalStatus.amountOfStethShares);
551+
console.log("amountOfAssets: ", withdrawalStatus.amountOfAssets);
552+
console.log("owner: ", withdrawalStatus.owner);
553+
console.log("timestamp: ", withdrawalStatus.timestamp);
554+
console.log("isFinalized: ", withdrawalStatus.isFinalized);
555+
console.log("isClaimed: ", withdrawalStatus.isClaimed);
531556
}
532557

533558
/**
534-
* @notice Test that onMorphoRepay reverts when called by non-Morpho
559+
* @notice Test that onMorphoRepay reverts when called without active context
535560
*/
536-
function test_revert_onMorphoRepay_only_morpho() public {
537-
console.log("\n=== Test: onMorphoRepay Only Morpho ===");
561+
function test_revert_onMorphoRepay_no_context() public {
562+
console.log("\n=== Test: onMorphoRepay No Context ===");
538563

539-
vm.expectRevert(MorphoLoopStrategy.UnauthorizedCallback.selector);
564+
vm.expectRevert(MorphoLoopStrategy.NoActiveContext.selector);
540565
morphoStrategy.onMorphoRepay(1 ether, "");
541566
}
542567

@@ -546,9 +571,7 @@ contract MorphoLoopStrategyTest is StvStrategyPoolHarness {
546571
function test_revert_exit_no_position() public {
547572
console.log("\n=== Test: Exit Reverts With No Position ===");
548573

549-
MorphoLoopStrategy.LoopExitParams memory exitParams = MorphoLoopStrategy.LoopExitParams({
550-
slippageBps: 100
551-
});
574+
MorphoLoopStrategy.LoopExitParams memory exitParams = MorphoLoopStrategy.LoopExitParams({slippageBps: 100});
552575

553576
vm.prank(USER1);
554577
vm.expectRevert(MorphoLoopStrategy.InsufficientCollateral.selector);

0 commit comments

Comments
 (0)