Skip to content

Commit

Permalink
feat(snippets): mm tests improvement
Browse files Browse the repository at this point in the history
  • Loading branch information
tomrpl committed Nov 14, 2023
1 parent 938a21f commit 356b663
Show file tree
Hide file tree
Showing 8 changed files with 366 additions and 90 deletions.
2 changes: 1 addition & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
@morpho-blue-test/=lib/morpho-blue/test/

@openzeppelin4/=lib/openzeppelin-contracts/contracts/
@openzeppelin5/=lib/metamorpho/lib/openzeppelin-contracts/contracts/
@openzeppelin/=lib/metamorpho/lib/openzeppelin-contracts/contracts/

@snippets/=src/

Expand Down
1 change: 1 addition & 0 deletions src/blue/BlueSnippets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {Id, IMorpho, MarketParams, Market} from "@morpho-blue/interfaces/IMorpho
import {IERC20} from "@morpho-blue/interfaces/IERC20.sol";
import {IIrm} from "@morpho-blue/interfaces/IIrm.sol";
import {IOracle} from "@morpho-blue/interfaces/IOracle.sol";

import {ERC20} from "@openzeppelin4/token/ERC20/ERC20.sol";
import {SafeERC20} from "@openzeppelin4/token/ERC20/utils/SafeERC20.sol";
import {MorphoBalancesLib} from "@morpho-blue/libraries/periphery/MorphoBalancesLib.sol";
Expand Down
181 changes: 181 additions & 0 deletions src/blue/CallbackSnippets.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// 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 {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
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;

IMorpho public immutable morpho;
SwapMock swapMock;

constructor(address morphoAddress) {
morpho = IMorpho(morphoAddress);
}

/*
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
*/

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 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);
}
}

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));

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

// function onMorphoFlashLoan(uint256 amount, bytes memory data) external {
// require(msg.sender == address(morpho));
// (bytes4 selector, bytes memory _data) = abi.decode(data, (bytes4, bytes));
// if (selector == this.flashLoan.selector) {
// assertEq(loanToken.balanceOf(address(this)), amount);
// loanToken.approve(address(morpho), amount);
// }
// }

// leverage function
function leverageMe(
uint256 leverageFactor,
uint256 loanLeverageFactor,
uint256 initialCollateral,
SwapMock _swapMock,
MarketParams calldata marketParams
) public {
_setSwapMock(_swapMock);

uint256 collateralAssets = initialCollateral * leverageFactor;
uint256 loanAmount = initialCollateral * loanLeverageFactor;

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

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

function liquidateWithoutCollat(
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 = 0;

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

// deleverage function
function deLeverageMe(
uint256 leverageFactor,
uint256 loanLeverageFactor,
uint256 initialCollateral,
SwapMock _swapMock,
MarketParams calldata marketParams
) public returns (uint256 amountRepayed) {
// might be redundant
_setSwapMock(_swapMock);

uint256 collateralAssets = initialCollateral * leverageFactor;
uint256 loanAmount = initialCollateral * loanLeverageFactor;

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

(amountRepayed,) = morpho.repay(
marketParams,
loanAmount,
0,
address(this),
abi.encode(this.deLeverageMe.selector, abi.encode(collateralAssets, marketParams))
);
}

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);
}
}

function _setSwapMock(SwapMock _swapMock) public {
swapMock = _swapMock;
}
}
43 changes: 43 additions & 0 deletions src/blue/mocks/SwapMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import {ORACLE_PRICE_SCALE} from "@morpho-blue/libraries/ConstantsLib.sol";

import "@morpho-blue/mocks/ERC20Mock.sol";
import {IOracle} from "@morpho-blue/interfaces/IOracle.sol";

import "@morpho-blue/libraries/MathLib.sol";

contract SwapMock {
using MathLib for uint256;

ERC20Mock public immutable collateralToken;
ERC20Mock public immutable loanToken;

address public immutable oracle;

constructor(address collateralAddress, address loanAddress, address oracleAddress) {
collateralToken = ERC20Mock(collateralAddress);
loanToken = ERC20Mock(loanAddress);

oracle = oracleAddress;
}

function swapCollatToLoan(uint256 amount) external returns (uint256 returnedAmount) {
returnedAmount = amount.mulDivDown(IOracle(oracle).price(), ORACLE_PRICE_SCALE);

collateralToken.transferFrom(msg.sender, address(this), amount);

loanToken.setBalance(address(this), returnedAmount);
loanToken.transfer(msg.sender, returnedAmount);
}

function swapLoanToCollat(uint256 amount) external returns (uint256 returnedAmount) {
returnedAmount = amount.mulDivDown(ORACLE_PRICE_SCALE, IOracle(oracle).price());

loanToken.transferFrom(msg.sender, address(this), amount);

collateralToken.setBalance(address(this), returnedAmount);
collateralToken.transfer(msg.sender, returnedAmount);
}
}
21 changes: 10 additions & 11 deletions src/metamorpho/MetamorphoSnippets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@ import {IrmMock} from "@metamorpho/mocks/IrmMock.sol";
import {MorphoBalancesLib} from "@morpho-blue/libraries/periphery/MorphoBalancesLib.sol";
import {MathLib, WAD} from "@morpho-blue/libraries/MathLib.sol";

import {Math} from "@openzeppelin5/utils/math/Math.sol";
import {ERC20} from "@openzeppelin5/token/ERC20/ERC20.sol";
import {Math} from "@openzeppelin/utils/math/Math.sol";
import {ERC20} from "@openzeppelin/token/ERC20/ERC20.sol";

contract MetamorphoSnippets {
uint256 constant FEE = 0.2 ether; // 20%

IMetaMorpho public immutable vault;
IMorpho public immutable morpho;
IMetaMorpho public immutable vault;

using MathLib for uint256;
using Math for uint256;
Expand All @@ -36,15 +35,16 @@ contract MetamorphoSnippets {
totalAssets = vault.lastTotalAssets();
}

/// @dev note that one can adapt the address in the call to the morpho contract
function vaultAmountInMarket(MarketParams memory marketParams) public view returns (uint256 vaultAmount) {
/// @dev note that one can adapt the address in the call to the morpho libs
function vaultAssetsInMarket(MarketParams memory marketParams) public view returns (uint256 vaultAmount) {
vaultAmount = morpho.expectedSupplyAssets(marketParams, address(vault));
}

function totalSharesUserVault(address user) public view returns (uint256 totalSharesUser) {
totalSharesUser = vault.balanceOf(user);
}

// The following function will return the current supply queue of the vault
function supplyQueueVault() public view returns (Id[] memory supplyQueueList) {
uint256 queueLength = vault.supplyQueueLength();
supplyQueueList = new Id[](queueLength);
Expand All @@ -54,6 +54,7 @@ contract MetamorphoSnippets {
return supplyQueueList;
}

// The following function will return the current withdraw queue of the vault
function withdrawQueueVault() public view returns (Id[] memory withdrawQueueList) {
uint256 queueLength = vault.supplyQueueLength();
withdrawQueueList = new Id[](queueLength);
Expand Down Expand Up @@ -85,8 +86,7 @@ contract MetamorphoSnippets {
supplyRate = borrowRate.wMulDown(1 ether - market.fee).wMulDown(utilization);
}

// TODO: edit comment + Test function
// same function as Morpho Blue Snippets
// TODO: edit comment
// a amount at 6%, B amount at 3 %:
// (a*6%) + (B*3%) / (a+b+ IDLE)

Expand All @@ -100,15 +100,14 @@ contract MetamorphoSnippets {
for (uint256 i; i < queueLength; ++i) {
Id idMarket = vault.withdrawQueue(i);

// To change once the cantina-review branch is merged
(address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) =
(morpho.idToMarketParams(idMarket));

MarketParams memory marketParams = MarketParams(loanToken, collateralToken, oracle, irm, lltv);
Market memory market = morpho.market(idMarket);

uint256 currentSupplyAPR = supplyAPRMarket(marketParams, market);
uint256 vaultAsset = vaultAmountInMarket(marketParams);
uint256 vaultAsset = vaultAssetsInMarket(marketParams);
ratio += currentSupplyAPR.wMulDown(vaultAsset);
}

Expand All @@ -133,5 +132,5 @@ contract MetamorphoSnippets {
}

// TODO:
// Reallocation example
// // Reallocation example
}
2 changes: 1 addition & 1 deletion test/forge/blue/TestBlueSnippets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ contract TestIntegrationSnippets is BaseTest {
vm.assume(market.totalSupplyAssets >= market.totalBorrowAssets);

(uint256 totalSupplyAssets,, uint256 totalBorrowAssets,) = morpho.expectedMarketBalances(marketParams);
uint256 borrowTrue = irm.borrowRate(marketParams, market);
uint256 borrowTrue = irm.borrowRateView(marketParams, market);
uint256 utilization = totalBorrowAssets == 0 ? 0 : totalBorrowAssets.wDivUp(totalSupplyAssets);

uint256 supplyTrue = borrowTrue.wMulDown(1 ether - market.fee).wMulDown(utilization);
Expand Down
Loading

0 comments on commit 356b663

Please sign in to comment.