Skip to content

Commit

Permalink
fix: review Jean
Browse files Browse the repository at this point in the history
  • Loading branch information
Jean-Grimal committed Nov 16, 2023
1 parent cb59621 commit 7caaeee
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 120 deletions.
145 changes: 72 additions & 73 deletions src/blue/CallbacksSnippets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,29 @@ import {Id, IMorpho, MarketParams, Market} from "@morpho-blue/interfaces/IMorpho
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 {MorphoBalancesLib} from "@morpho-blue/libraries/periphery/MorphoBalancesLib.sol";
import {MarketParamsLib} from "@morpho-blue/libraries/MarketParamsLib.sol";

/*
The following implementation regarding the swap mocked has been done for educationnal purpose
the swap mock is giving back, thanks to the orace of the market, the exact value in terms of amount between a
collateral and a loan token
One should be aware that has to be taken into account on potential swap:
1. slippage
2. fees
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 introduction of the SwapMock 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
`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
add a definition of what snippets are
*/
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;
Expand All @@ -42,68 +46,74 @@ contract CallbacksSnippets is IMorphoSupplyCollateralCallback, IMorphoRepayCallb
/*
Callbacks
remember that at a given market, one can leverage itself up to 1/1-LLTV,
leverageFactor so for an LLTV of 80% -> 5 is the max leverage factor
loanLeverageFactor max loanLeverageFactor would have to be on LLTV * leverageFactor to be safe
Reminder: for a given market, one can leverage his position up to a leverageFactor = 1/1-LLTV,
Example : with a LLTV of 80% -> 5 is the max leverage factor
*/

function onMorphoSupplyCollateral(uint256 amount, bytes calldata data) external {
require(msg.sender == address(morpho));
(bytes4 selector, bytes memory _data) = abi.decode(data, (bytes4, bytes));
if (selector == this.leverageMe.selector) {
(uint256 toBorrow, MarketParams memory marketParams) = abi.decode(_data, (uint256, MarketParams));
(uint256 amountBis,) = morpho.borrow(marketParams, toBorrow, 0, address(this), address(this));
ERC20(marketParams.collateralToken).approve(address(swapMock), amount);

// Logic to Implement. Following example is a swap, could be a 'unwrap + stake + wrap staked' for
// wETH(wstETH) Market
swapMock.swapLoanToCollat(amountBis);
}
function onMorphoSupplyCollateral(uint256 amount, bytes calldata data) external onlyMorpho {
(uint256 toBorrow, MarketParams memory marketParams, address user) =
abi.decode(data, (uint256, MarketParams, address));
(uint256 amountBis,) = morpho.borrow(marketParams, toBorrow, 0, user, address(this));

ERC20(marketParams.loanToken).approve(address(swapMock), 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);
}

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

function onMorphoRepay(uint256 amount, bytes calldata data) external {
require(msg.sender == address(morpho));
(bytes4 selector, bytes memory _data) = abi.decode(data, (bytes4, bytes));
if (selector == this.deLeverageMe.selector) {
(uint256 toWithdraw, MarketParams memory marketParams) = abi.decode(_data, (uint256, MarketParams));
morpho.withdrawCollateral(marketParams, toWithdraw, address(this), address(this));
function onMorphoRepay(uint256 amount, bytes calldata data) external onlyMorpho {
(MarketParams memory marketParams, address user) = abi.decode(data, (MarketParams, address));
uint256 toWithdraw = morpho.collateral(marketParams.id(), user);

ERC20(marketParams.loanToken).approve(address(morpho), amount);
swapMock.swapCollatToLoan(toWithdraw);
}
morpho.withdrawCollateral(marketParams, toWithdraw, user, address(this));

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

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

uint256 collateralAssets = collateralInitAmount * leverageFactor;
uint256 loanAmount = collateralInitAmount * loanLeverageFactor;
ERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), initAmountCollateral);

_approveMaxTo(address(marketParams.collateralToken), address(this));
uint256 finalAmountcollateral = initAmountCollateral * leverageFactor;

// The amount of LoanToken to be borrowed (and then swapped against collateralToken) to perform the callback is
// the following :

// (leverageFactor - 1) * InitAmountCollateral.mulDivDown.(ORACLE_PRICE_SCALE, IOracle(oracle).price())

// However here we have price = `ORACLE_PRICE_SCALE`, so loanAmount = (leverageFactor - 1) *
// InitAmountCollateral

// Warning : When using real swaps, price doesn't equal `ORACLE_PRICE_SCALE` anymore, so
// mulDivDown.(ORACLE_PRICE_SCALE, IOracle(oracle).price()) can't be removed from the calculus, and therefore an
// oracle should be used to compute the correct amount.
// Warning : When using real swaps, fees and slippage should also be taken into account to compute `loanAmount`.

uint256 loanAmount = (leverageFactor - 1) * initAmountCollateral;

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

morpho.supplyCollateral(
marketParams,
collateralAssets,
address(this),
abi.encode(this.leverageMe.selector, abi.encode(loanAmount, marketParams))
marketParams, finalAmountcollateral, msg.sender, abi.encode(loanAmount, marketParams, msg.sender)
);
}

Expand All @@ -120,35 +130,24 @@ contract CallbacksSnippets is IMorphoSupplyCollateralCallback, IMorphoRepayCallb

uint256 repaidShares = 0;

(seizedAssets, repaidAssets) = morpho.liquidate(
marketParams,
borrower,
assetsToSeize,
repaidShares,
abi.encode(this.liquidateWithoutCollat.selector, abi.encode(loanAmountToRepay))
);
(seizedAssets, repaidAssets) =
morpho.liquidate(marketParams, borrower, assetsToSeize, repaidShares, abi.encode(loanAmountToRepay));
}

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

uint256 collateralAssets = collateralInitAmount * leverageFactor;
uint256 loanAmount = collateralInitAmount * loanLeverageFactor;
uint256 totalShares = morpho.borrowShares(marketParams.id(), msg.sender);

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

(amountRepayed,) = morpho.repay(marketParams, 0, totalShares, msg.sender, abi.encode(marketParams, msg.sender));

(amountRepayed,) = morpho.repay(
marketParams,
loanAmount,
0,
address(this),
abi.encode(this.deLeverageMe.selector, abi.encode(collateralAssets, marketParams))
ERC20(marketParams.collateralToken).safeTransfer(
msg.sender, ERC20(marketParams.collateralToken).balanceOf(msg.sender)
);
}

Expand Down
84 changes: 37 additions & 47 deletions test/forge/blue/TestCallbacksSnippets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,93 +12,83 @@ contract CallbacksIntegrationTest is BaseTest {
using MarketParamsLib for MarketParams;
using SharesMathLib for uint256;

address internal USER;

SwapMock internal swapMock;

CallbacksSnippets public snippets;

function setUp() public virtual override {
super.setUp();

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

vm.startPrank(USER);
collateralToken.approve(address(snippets), type(uint256).max);
morpho.setAuthorization(address(snippets), true);
vm.stopPrank();
}

function testLeverageMe(uint256 collateralInitAmount) public {
function testLeverageMe(uint256 initAmountCollateral) public {
// INITIALISATION

uint256 leverageFactor = 4; // nb to set
uint256 loanLeverageFactor = 3; // max here would be 3.2 = 0.8 * leverageFactor

collateralInitAmount = bound(collateralInitAmount, MIN_TEST_AMOUNT, MAX_TEST_AMOUNT / leverageFactor);

collateralToken.setBalance(address(snippets), collateralInitAmount);

uint256 collateralAmount = collateralInitAmount * leverageFactor;
initAmountCollateral = bound(initAmountCollateral, MIN_TEST_AMOUNT, MAX_TEST_AMOUNT / leverageFactor);
uint256 finalAmountCollateral = initAmountCollateral * leverageFactor;

oracle.setPrice(ORACLE_PRICE_SCALE);

// supplying enough liquidity in the market
vm.startPrank(SUPPLIER);
loanToken.setBalance(address(SUPPLIER), collateralAmount);
morpho.supply(marketParams, collateralAmount, 0, address(SUPPLIER), hex"");
loanToken.setBalance(address(SUPPLIER), finalAmountCollateral);
morpho.supply(marketParams, finalAmountCollateral, 0, address(SUPPLIER), hex"");
vm.stopPrank();

// approve the swap contract
vm.startPrank(address(snippets));
loanToken.approve(address(morpho), type(uint256).max);
loanToken.approve(address(swapMock), type(uint256).max);
collateralToken.approve(address(morpho), type(uint256).max);
collateralToken.approve(address(swapMock), type(uint256).max);
vm.stopPrank();

uint256 loanAmount = collateralInitAmount * loanLeverageFactor;
collateralToken.setBalance(USER, initAmountCollateral);
vm.prank(USER);
snippets.leverageMe(leverageFactor, initAmountCollateral, swapMock, marketParams);

snippets.leverageMe(leverageFactor, loanLeverageFactor, collateralInitAmount, swapMock, marketParams);
uint256 loanAmount = initAmountCollateral * (leverageFactor - 1);

assertGt(morpho.borrowShares(marketParams.id(), address(snippets)), 0, "no borrow");
assertEq(morpho.collateral(marketParams.id(), address(snippets)), collateralAmount, "no collateral");
assertEq(morpho.expectedBorrowAssets(marketParams, address(snippets)), loanAmount, "no collateral");
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");
}

function testDeLeverageMe(uint256 collateralInitAmount) public {
function testDeLeverageMe(uint256 initAmountCollateral) public {
/// same as testLeverageMe

uint256 leverageFactor = 4; // nb to set
uint256 loanLeverageFactor = 3; // max here would be 3.2 = 0.8 * leverageFactor

collateralInitAmount = bound(collateralInitAmount, MIN_TEST_AMOUNT, MAX_TEST_AMOUNT / leverageFactor);

collateralToken.setBalance(address(snippets), collateralInitAmount);

uint256 collateralAmount = collateralInitAmount * leverageFactor;
initAmountCollateral = bound(initAmountCollateral, MIN_TEST_AMOUNT, MAX_TEST_AMOUNT / leverageFactor);
uint256 finalAmountCollateral = initAmountCollateral * leverageFactor;

oracle.setPrice(ORACLE_PRICE_SCALE);

vm.startPrank(SUPPLIER);
loanToken.setBalance(address(SUPPLIER), collateralAmount);
morpho.supply(marketParams, collateralAmount, 0, address(SUPPLIER), hex"");
loanToken.setBalance(address(SUPPLIER), finalAmountCollateral);
morpho.supply(marketParams, finalAmountCollateral, 0, address(SUPPLIER), hex"");
vm.stopPrank();

vm.startPrank(address(snippets));
loanToken.approve(address(morpho), type(uint256).max);
loanToken.approve(address(swapMock), type(uint256).max);
collateralToken.approve(address(morpho), type(uint256).max);
collateralToken.approve(address(swapMock), type(uint256).max);
vm.stopPrank();

uint256 loanAmount = collateralInitAmount * loanLeverageFactor;
uint256 loanAmount = initAmountCollateral * (leverageFactor - 1);

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

assertGt(morpho.borrowShares(marketParams.id(), address(snippets)), 0, "no borrow");
assertEq(morpho.collateral(marketParams.id(), address(snippets)), collateralAmount, "no collateral");
assertEq(morpho.expectedBorrowAssets(marketParams, address(snippets)), loanAmount, "no collateral");
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(leverageFactor, loanLeverageFactor, collateralInitAmount, swapMock, marketParams);

assertEq(morpho.borrowShares(marketParams.id(), address(snippets)), 0, "no borrow");
assertEq(morpho.borrowShares(marketParams.id(), USER), 0, "no borrow");
assertEq(amountRepayed, loanAmount, "no repaid");
}

Expand Down

0 comments on commit 7caaeee

Please sign in to comment.