-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #15 from morpho-org/feat/callbacks
Feat/callbacks
- Loading branch information
Showing
13 changed files
with
584 additions
and
104 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Submodule metamorpho
updated
28 files
+3 −3 | .github/workflows/foundry.yml | |
+53 −22 | README.md | |
+1 −1 | lib/morpho-blue | |
+215 −189 | src/MetaMorpho.sol | |
+41 −36 | src/interfaces/IMetaMorpho.sol | |
+24 −6 | src/libraries/ErrorsLib.sol | |
+14 −13 | src/libraries/EventsLib.sol | |
+55 −0 | src/libraries/PendingLib.sol | |
+2 −0 | src/mocks/ERC20Mock.sol | |
+4 −1 | test/forge/ERC4626ComplianceTest.sol | |
+12 −15 | test/forge/ERC4626Test.sol | |
+33 −6 | test/forge/FeeTest.sol | |
+69 −17 | test/forge/GuardianTest.sol | |
+63 −90 | test/forge/MarketTest.sol | |
+1 −1 | test/forge/MetaMorphoFactoryTest.sol | |
+1 −4 | test/forge/MetaMorphoInternalTest.sol | |
+2 −2 | test/forge/MulticallTest.sol | |
+38 −21 | test/forge/PermitTest.sol | |
+15 −16 | test/forge/ReallocateIdleTest.sol | |
+74 −109 | test/forge/ReallocateWithdrawTest.sol | |
+140 −0 | test/forge/RevokeTest.sol | |
+6 −8 | test/forge/RoleTest.sol | |
+203 −115 | test/forge/TimelockTest.sol | |
+27 −29 | test/forge/UrdTest.sol | |
+18 −2 | test/forge/helpers/BaseTest.sol | |
+61 −26 | test/forge/helpers/IntegrationTest.sol | |
+49 −28 | test/hardhat/MetaMorpho.spec.ts | |
+78 −75 | test/metamorpho_tests.tree |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,14 @@ | ||
@forge-std/=lib/morpho-blue/lib/forge-std/src/ | ||
|
||
@morpho-blue/=lib/morpho-blue/src/ | ||
@morpho-blue-test/=lib/morpho-blue/test/ | ||
@morpho-blue-test/=lib/morpho-blue/test/forge/ | ||
|
||
@openzeppelin4/=lib/openzeppelin-contracts/contracts/ | ||
@openzeppelin5/=lib/metamorpho/lib/openzeppelin-contracts/contracts/ | ||
@openzeppelin/=lib/metamorpho/lib/openzeppelin-contracts/contracts/ | ||
|
||
@snippets/=src/ | ||
|
||
@solmate/=lib/morpho-blue/lib/solmate/src/ | ||
solmate/=lib/morpho-blue/lib/permit2/lib/solmate/ | ||
@solmate/=lib/solmate/src/ | ||
|
||
@metamorpho/=lib/metamorpho/src/ | ||
@metamorpho-test/=lib/metamorpho/test/forge/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
// 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 {MarketParamsLib} from "@morpho-blue/libraries/MarketParamsLib.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 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 | ||
*/ | ||
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; | ||
|
||
constructor(address morphoAddress) { | ||
morpho = IMorpho(morphoAddress); | ||
} | ||
|
||
/* | ||
Callbacks | ||
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 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 { | ||
(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 onlyMorpho { | ||
(MarketParams memory marketParams, address user) = abi.decode(data, (MarketParams, address)); | ||
uint256 toWithdraw = morpho.collateral(marketParams.id(), user); | ||
|
||
morpho.withdrawCollateral(marketParams, toWithdraw, user, address(this)); | ||
|
||
ERC20(marketParams.collateralToken).approve(address(swapMock), amount); | ||
swapMock.swapCollatToLoan(amount); | ||
} | ||
|
||
function leverageMe( | ||
uint256 leverageFactor, | ||
uint256 initAmountCollateral, | ||
SwapMock _swapMock, | ||
MarketParams calldata marketParams | ||
) public { | ||
_setSwapMock(_swapMock); | ||
|
||
ERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), initAmountCollateral); | ||
|
||
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, finalAmountcollateral, msg.sender, abi.encode(loanAmount, marketParams, msg.sender) | ||
); | ||
} | ||
|
||
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; | ||
|
||
(seizedAssets, repaidAssets) = | ||
morpho.liquidate(marketParams, borrower, assetsToSeize, repaidShares, abi.encode(loanAmountToRepay)); | ||
} | ||
|
||
function deLeverageMe(SwapMock _swapMock, MarketParams calldata marketParams) | ||
public | ||
returns (uint256 amountRepayed) | ||
{ | ||
_setSwapMock(_swapMock); | ||
|
||
uint256 totalShares = morpho.borrowShares(marketParams.id(), msg.sender); | ||
|
||
_approveMaxTo(marketParams.loanToken, address(morpho)); | ||
|
||
(amountRepayed,) = morpho.repay(marketParams, 0, totalShares, msg.sender, abi.encode(marketParams, msg.sender)); | ||
|
||
ERC20(marketParams.collateralToken).safeTransfer( | ||
msg.sender, ERC20(marketParams.collateralToken).balanceOf(msg.sender) | ||
); | ||
} | ||
|
||
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.