Skip to content

Commit

Permalink
Merge pull request #16 from morpho-org/feat/adapt-swap
Browse files Browse the repository at this point in the history
feat(swap): adapting the swap with an interface ISwap
  • Loading branch information
tomrpl authored Nov 24, 2023
2 parents 78adf91 + 170994b commit 3c39483
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 51 deletions.
73 changes: 28 additions & 45 deletions src/blue/CallbacksSnippets.sol
Original file line number Diff line number Diff line change
@@ -1,46 +1,51 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;

import {SwapMock} from "@snippets/blue/mocks/SwapMock.sol";
import {
IMorphoSupplyCollateralCallback,
IMorphoLiquidateCallback,
IMorphoRepayCallback
} from "@morpho-blue/interfaces/IMorphoCallbacks.sol";

import {Id, IMorpho, MarketParams, Market} from "@morpho-blue/interfaces/IMorpho.sol";
import {Id, IMorpho, MarketParams} from "@morpho-blue/interfaces/IMorpho.sol";
import {SafeTransferLib, ERC20} from "@solmate/utils/SafeTransferLib.sol";
import {MathLib} from "@morpho-blue/libraries/MathLib.sol";
import {MorphoLib} from "@morpho-blue/libraries/periphery/MorphoLib.sol";
import {MarketParamsLib} from "@morpho-blue/libraries/MarketParamsLib.sol";

import {ISwap} from "@snippets/blue/interfaces/ISwap.sol";
/*
The SwapMock contract only has educational purpose. It simulates a contract allowing to swap a token against another,
with the exact price returned by an arbitrary oracle.
The following swapper contract only has educational purpose. It simulates a contract allowing to swap a token against
another, with the exact price returned by an arbitrary oracle.
The introduction of the SwapMock contract is to showcase the functioning of leverage on Morpho Blue (using callbacks)
The introduction of the swapper contract is to showcase the functioning of leverage on Morpho Blue (using callbacks)
without highlighting any known DEX.
Therefore, SwapMock must be replaced (by the swap of your choice) in your implementation. The functions
Therefore, swapper must be replaced (by the swap of your choice) in your implementation. The functions
`swapCollatToLoan` and `swapLoanToCollat` must as well be adapted to match the ones of the chosen swap contract.
One should be aware that has to be taken into account on potential swap:
1. slippage
2. fees
add a definition of what snippets are
TODOS: add a definition of what snippets are useful for
*/

contract CallbacksSnippets is IMorphoSupplyCollateralCallback, IMorphoRepayCallback, IMorphoLiquidateCallback {
using MathLib for uint256;
using MorphoLib for IMorpho;
using MarketParamsLib for MarketParams;
using SafeTransferLib for ERC20;

IMorpho public immutable morpho;
SwapMock swapMock;
ISwap public immutable swapper;

constructor(IMorpho _morpho, ISwap _swapper) {
morpho = _morpho;
swapper = _swapper;
}

constructor(address morphoAddress) {
morpho = IMorpho(morphoAddress);
modifier onlyMorpho() {
require(msg.sender == address(morpho), "msg.sender should be Morpho Blue");
_;
}

/*
Expand All @@ -58,19 +63,19 @@ contract CallbacksSnippets is IMorphoSupplyCollateralCallback, IMorphoRepayCallb
abi.decode(data, (uint256, MarketParams, address));
(uint256 amountBis,) = morpho.borrow(marketParams, toBorrow, 0, user, address(this));

ERC20(marketParams.loanToken).approve(address(swapMock), amount);
ERC20(marketParams.loanToken).approve(address(swapper), amount);

// Logic to Implement. Following example is a swap, could be a 'unwrap + stake + wrap staked' for
// wETH(wstETH) Market
// _approveMaxTo(marketParams.);
swapMock.swapLoanToCollat(amountBis);
swapper.swapLoanToCollat(amountBis);
}

function onMorphoLiquidate(uint256 repaidAssets, bytes calldata data) external onlyMorpho {
(uint256 toSwap, MarketParams memory marketParams) = abi.decode(data, (uint256, MarketParams));
uint256 returnedAmount = swapMock.swapCollatToLoan(toSwap);
uint256 returnedAmount = swapper.swapCollatToLoan(toSwap);
require(returnedAmount > repaidAssets); // Add logic for gas cost threshold for instance
ERC20(marketParams.loanToken).approve(address(swapMock), returnedAmount);
ERC20(marketParams.loanToken).approve(address(swapper), returnedAmount);
}

function onMorphoRepay(uint256 amount, bytes calldata data) external onlyMorpho {
Expand All @@ -79,18 +84,13 @@ contract CallbacksSnippets is IMorphoSupplyCollateralCallback, IMorphoRepayCallb

morpho.withdrawCollateral(marketParams, toWithdraw, user, address(this));

ERC20(marketParams.collateralToken).approve(address(swapMock), amount);
swapMock.swapCollatToLoan(amount);
ERC20(marketParams.collateralToken).approve(address(swapper), amount);
swapper.swapCollatToLoan(amount);
}

function leverageMe(
uint256 leverageFactor,
uint256 initAmountCollateral,
SwapMock _swapMock,
MarketParams calldata marketParams
) public {
_setSwapMock(_swapMock);

function leverageMe(uint256 leverageFactor, uint256 initAmountCollateral, MarketParams calldata marketParams)
public
{
ERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), initAmountCollateral);

uint256 finalAmountcollateral = initAmountCollateral * leverageFactor;
Expand Down Expand Up @@ -121,11 +121,8 @@ contract CallbacksSnippets is IMorphoSupplyCollateralCallback, IMorphoRepayCallb
address borrower,
uint256 loanAmountToRepay,
uint256 assetsToSeize,
SwapMock _swapMock,
MarketParams calldata marketParams
) public returns (uint256 seizedAssets, uint256 repaidAssets) {
_setSwapMock(_swapMock);

_approveMaxTo(address(marketParams.collateralToken), address(this));

uint256 repaidShares;
Expand All @@ -134,12 +131,7 @@ contract CallbacksSnippets is IMorphoSupplyCollateralCallback, IMorphoRepayCallb
morpho.liquidate(marketParams, borrower, assetsToSeize, repaidShares, abi.encode(loanAmountToRepay));
}

function deLeverageMe(SwapMock _swapMock, MarketParams calldata marketParams)
public
returns (uint256 amountRepayed)
{
_setSwapMock(_swapMock);

function deLeverageMe(MarketParams calldata marketParams) public returns (uint256 amountRepayed) {
uint256 totalShares = morpho.borrowShares(marketParams.id(), msg.sender);

_approveMaxTo(marketParams.loanToken, address(morpho));
Expand All @@ -151,18 +143,9 @@ contract CallbacksSnippets is IMorphoSupplyCollateralCallback, IMorphoRepayCallb
);
}

modifier onlyMorpho() {
require(msg.sender == address(morpho), "msg.sender should be Morpho Blue");
_;
}

function _approveMaxTo(address asset, address spender) internal {
if (ERC20(asset).allowance(address(this), spender) == 0) {
ERC20(asset).approve(spender, type(uint256).max);
ERC20(asset).safeApprove(spender, type(uint256).max);
}
}

function _setSwapMock(SwapMock _swapMock) public {
swapMock = _swapMock;
}
}
22 changes: 22 additions & 0 deletions src/blue/interfaces/ISwap.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;

/**
* @title ISwap
* @dev Interface for the SwapMock contract.
*/
interface ISwap {
/**
* @dev Swaps collateral token to loan token.
* @param amount The amount of collateral token to swap.
* @return returnedAmount The amount of loan token returned.
*/
function swapCollatToLoan(uint256 amount) external returns (uint256 returnedAmount);

/**
* @dev Swaps loan token to collateral token.
* @param amount The amount of loan token to swap.
* @return returnedAmount The amount of collateral token returned.
*/
function swapLoanToCollat(uint256 amount) external returns (uint256 returnedAmount);
}
13 changes: 7 additions & 6 deletions test/forge/blue/TestCallbacksSnippets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.0;

import "@morpho-blue-test/BaseTest.sol";
import {ISwap} from "@snippets/blue/interfaces/ISwap.sol";
import {SwapMock} from "@snippets/blue/mocks/SwapMock.sol";
import {CallbacksSnippets} from "@snippets/blue/CallbacksSnippets.sol";

Expand All @@ -14,7 +15,7 @@ contract CallbacksIntegrationTest is BaseTest {

address internal USER;

SwapMock internal swapMock;
ISwap internal swapper;

CallbacksSnippets public snippets;

Expand All @@ -23,8 +24,8 @@ contract CallbacksIntegrationTest is BaseTest {

USER = makeAddr("User");

swapMock = new SwapMock(address(collateralToken), address(loanToken), address(oracle));
snippets = new CallbacksSnippets(address(morpho)); // todos add the addres of WETH, lido, wsteth
swapper = ISwap(address(new SwapMock(address(collateralToken), address(loanToken), address(oracle))));
snippets = new CallbacksSnippets(morpho, swapper);

vm.startPrank(USER);
collateralToken.approve(address(snippets), type(uint256).max);
Expand All @@ -50,7 +51,7 @@ contract CallbacksIntegrationTest is BaseTest {

collateralToken.setBalance(USER, initAmountCollateral);
vm.prank(USER);
snippets.leverageMe(leverageFactor, initAmountCollateral, swapMock, marketParams);
snippets.leverageMe(leverageFactor, initAmountCollateral, marketParams);

uint256 loanAmount = initAmountCollateral * (leverageFactor - 1);

Expand Down Expand Up @@ -78,15 +79,15 @@ contract CallbacksIntegrationTest is BaseTest {

collateralToken.setBalance(USER, initAmountCollateral);
vm.prank(USER);
snippets.leverageMe(leverageFactor, initAmountCollateral, swapMock, marketParams);
snippets.leverageMe(leverageFactor, initAmountCollateral, marketParams);

assertGt(morpho.borrowShares(marketParams.id(), USER), 0, "no borrow");
assertEq(morpho.collateral(marketParams.id(), USER), finalAmountCollateral, "no collateral");
assertEq(morpho.expectedBorrowAssets(marketParams, USER), loanAmount, "no collateral");

/// end of testLeverageMe
vm.prank(USER);
uint256 amountRepayed = snippets.deLeverageMe(swapMock, marketParams);
uint256 amountRepayed = snippets.deLeverageMe(marketParams);

assertEq(morpho.borrowShares(marketParams.id(), USER), 0, "no borrow");
assertEq(amountRepayed, loanAmount, "no repaid");
Expand Down

0 comments on commit 3c39483

Please sign in to comment.