diff --git a/lib/metamorpho b/lib/metamorpho index 9c66465..74d51b9 160000 --- a/lib/metamorpho +++ b/lib/metamorpho @@ -1 +1 @@ -Subproject commit 9c66465f00a76ad078c2ddf2387f8545cad35e78 +Subproject commit 74d51b9f5bb1fb484f0db0e8f7aa67d0f62bcc78 diff --git a/remappings.txt b/remappings.txt index d776195..8588a3a 100644 --- a/remappings.txt +++ b/remappings.txt @@ -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/ diff --git a/src/blue/BlueSnippets.sol b/src/blue/BlueSnippets.sol index b02cb64..59868d7 100644 --- a/src/blue/BlueSnippets.sol +++ b/src/blue/BlueSnippets.sol @@ -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"; diff --git a/src/blue/CallbackSnippets.sol b/src/blue/CallbackSnippets.sol new file mode 100644 index 0000000..9eeeba3 --- /dev/null +++ b/src/blue/CallbackSnippets.sol @@ -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; + } +} diff --git a/src/blue/mocks/SwapMock.sol b/src/blue/mocks/SwapMock.sol new file mode 100644 index 0000000..731b778 --- /dev/null +++ b/src/blue/mocks/SwapMock.sol @@ -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); + } +} diff --git a/src/metamorpho/MetamorphoSnippets.sol b/src/metamorpho/MetamorphoSnippets.sol index 66dd792..58e11e9 100644 --- a/src/metamorpho/MetamorphoSnippets.sol +++ b/src/metamorpho/MetamorphoSnippets.sol @@ -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; @@ -36,8 +35,8 @@ 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)); } @@ -45,6 +44,7 @@ contract MetamorphoSnippets { 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); @@ -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); @@ -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) @@ -100,7 +100,6 @@ 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)); @@ -108,7 +107,7 @@ contract MetamorphoSnippets { Market memory market = morpho.market(idMarket); uint256 currentSupplyAPR = supplyAPRMarket(marketParams, market); - uint256 vaultAsset = vaultAmountInMarket(marketParams); + uint256 vaultAsset = vaultAssetsInMarket(marketParams); ratio += currentSupplyAPR.wMulDown(vaultAsset); } @@ -133,5 +132,5 @@ contract MetamorphoSnippets { } // TODO: - // Reallocation example + // // Reallocation example } diff --git a/test/forge/blue/TestBlueSnippets.sol b/test/forge/blue/TestBlueSnippets.sol index bc70501..4807f25 100644 --- a/test/forge/blue/TestBlueSnippets.sol +++ b/test/forge/blue/TestBlueSnippets.sol @@ -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); diff --git a/test/forge/metamorpho/TestMetamorphoSnippets.sol b/test/forge/metamorpho/TestMetamorphoSnippets.sol index 8f961b7..cb41ef2 100644 --- a/test/forge/metamorpho/TestMetamorphoSnippets.sol +++ b/test/forge/metamorpho/TestMetamorphoSnippets.sol @@ -4,7 +4,8 @@ pragma solidity ^0.8.0; import {MetamorphoSnippets} from "@snippets/metamorpho/MetamorphoSnippets.sol"; import "@metamorpho-test/helpers/IntegrationTest.sol"; import "@morpho-blue/libraries/SharesMathLib.sol"; -import {SafeCast} from "@openzeppelin5/utils/math/SafeCast.sol"; +import {MorphoBalancesLib} from "@morpho-blue/libraries/periphery/MorphoBalancesLib.sol"; +import {SafeCast} from "@openzeppelin/utils/math/SafeCast.sol"; contract TestIntegrationSnippets is IntegrationTest { MetamorphoSnippets internal snippets; @@ -19,6 +20,8 @@ contract TestIntegrationSnippets is IntegrationTest { function setUp() public virtual override { super.setUp(); snippets = new MetamorphoSnippets(address(vault), address(morpho)); + _setCap(allMarkets[0], CAP); + _sortSupplyQueueIdleLast(); } function testTotalDepositVault(uint256 deposited) public { @@ -43,36 +46,19 @@ contract TestIntegrationSnippets is IntegrationTest { assertEq(firstDeposit + secondDeposit, snippetTotalAsset2, "lastTotalAssets2"); } - function testDeposit(uint256 assets) public { - _setCap(allMarkets[0], CAP); + function testVaultAssetsInMarket(uint256 assets) public { assets = bound(assets, MIN_TEST_ASSETS, MAX_TEST_ASSETS); loanToken.setBalance(SUPPLIER, assets); - vm.expectEmit(); - emit EventsLib.UpdateLastTotalAssets(vault.totalAssets() + assets); vm.prank(SUPPLIER); uint256 shares = vault.deposit(assets, ONBEHALF); assertGt(shares, 0, "shares"); assertEq(vault.balanceOf(ONBEHALF), shares, "balanceOf(ONBEHALF)"); assertEq(morpho.expectedSupplyAssets(allMarkets[0], address(vault)), assets, "expectedSupplyAssets(vault)"); - } - function testVaultAmountInMarket(uint256 assets) public { - _setCap(allMarkets[0], CAP); - assets = bound(assets, MIN_TEST_ASSETS, MAX_TEST_ASSETS); - - loanToken.setBalance(SUPPLIER, assets); - - vm.prank(SUPPLIER); - uint256 shares = vault.deposit(assets, ONBEHALF); - - assertGt(shares, 0, "shares"); - assertEq(vault.balanceOf(ONBEHALF), shares, "balanceOf(ONBEHALF)"); - assertEq(morpho.expectedSupplyAssets(allMarkets[0], address(vault)), assets, "expectedSupplyAssets(vault)"); - - uint256 vaultAmount = snippets.vaultAmountInMarket(allMarkets[0]); + uint256 vaultAmount = snippets.vaultAssetsInMarket(allMarkets[0]); assertEq(assets, vaultAmount, "expectedSupplyAssets(vault)"); } @@ -91,11 +77,6 @@ contract TestIntegrationSnippets is IntegrationTest { function testSupplyQueueVault() public { _setCaps(); - - assertEq(Id.unwrap(vault.supplyQueue(0)), Id.unwrap(allMarkets[0].id())); - assertEq(Id.unwrap(vault.supplyQueue(1)), Id.unwrap(allMarkets[1].id())); - assertEq(Id.unwrap(vault.supplyQueue(2)), Id.unwrap(allMarkets[2].id())); - Id[] memory supplyQueue = new Id[](2); supplyQueue[0] = allMarkets[1].id(); supplyQueue[1] = allMarkets[2].id(); @@ -103,9 +84,10 @@ contract TestIntegrationSnippets is IntegrationTest { vm.prank(ALLOCATOR); vault.setSupplyQueue(supplyQueue); - Id[] memory supplyQueueList = snippets.supplyQueueVault(); assertEq(Id.unwrap(vault.supplyQueue(0)), Id.unwrap(allMarkets[1].id())); assertEq(Id.unwrap(vault.supplyQueue(1)), Id.unwrap(allMarkets[2].id())); + + Id[] memory supplyQueueList = snippets.supplyQueueVault(); assertEq(Id.unwrap(supplyQueueList[0]), Id.unwrap(allMarkets[1].id())); assertEq(Id.unwrap(supplyQueueList[1]), Id.unwrap(allMarkets[2].id())); } @@ -113,33 +95,34 @@ contract TestIntegrationSnippets is IntegrationTest { function testWithdrawQueueVault() public { _setCaps(); - assertEq(Id.unwrap(vault.withdrawQueue(0)), Id.unwrap(allMarkets[0].id())); - assertEq(Id.unwrap(vault.withdrawQueue(1)), Id.unwrap(allMarkets[1].id())); - assertEq(Id.unwrap(vault.withdrawQueue(2)), Id.unwrap(allMarkets[2].id())); - - uint256[] memory indexes = new uint256[](3); + uint256[] memory indexes = new uint256[](4); indexes[0] = 1; indexes[1] = 2; - indexes[2] = 0; + indexes[2] = 3; + indexes[3] = 0; - Id[] memory expectedWithdrawQueue = new Id[](3); - expectedWithdrawQueue[0] = allMarkets[1].id(); - expectedWithdrawQueue[1] = allMarkets[2].id(); - expectedWithdrawQueue[2] = allMarkets[0].id(); + Id[] memory expectedWithdrawQueue = new Id[](4); + expectedWithdrawQueue[0] = allMarkets[0].id(); + expectedWithdrawQueue[1] = allMarkets[1].id(); + expectedWithdrawQueue[2] = allMarkets[2].id(); + expectedWithdrawQueue[3] = idleParams.id(); + vm.expectEmit(address(vault)); + emit EventsLib.SetWithdrawQueue(ALLOCATOR, expectedWithdrawQueue); vm.prank(ALLOCATOR); vault.updateWithdrawQueue(indexes); - Id[] memory withdrawQueueList = snippets.withdrawQueueVault(); assertEq(Id.unwrap(vault.withdrawQueue(0)), Id.unwrap(expectedWithdrawQueue[0])); assertEq(Id.unwrap(vault.withdrawQueue(1)), Id.unwrap(expectedWithdrawQueue[1])); assertEq(Id.unwrap(vault.withdrawQueue(2)), Id.unwrap(expectedWithdrawQueue[2])); + assertEq(Id.unwrap(vault.withdrawQueue(3)), Id.unwrap(expectedWithdrawQueue[3])); + + Id[] memory withdrawQueueList = snippets.withdrawQueueVault(); assertEq(Id.unwrap(withdrawQueueList[0]), Id.unwrap(expectedWithdrawQueue[0])); assertEq(Id.unwrap(withdrawQueueList[1]), Id.unwrap(expectedWithdrawQueue[1])); } - // OK function testCapMarket(MarketParams memory marketParams) public { Id idMarket = marketParams.id(); uint192 cap = vault.config(idMarket).cap; @@ -147,44 +130,113 @@ contract TestIntegrationSnippets is IntegrationTest { assertEq(cap, snippetCap, "cap per market"); } - // TODO Implement the TEST SUPPLY APR EQUAL 0 Function - // function testSupplyAPREqual0(MarketParams memory marketParams, Market memory market) public { - // vm.assume(market.totalBorrowAssets == 0); - // vm.assume(market.totalBorrowShares == 0); - // vm.assume(market.totalSupplyAssets > 100000); - // vm.assume(market.lastUpdate > 0); - // vm.assume(market.fee < 1 ether); - // vm.assume(market.totalSupplyAssets >= market.totalBorrowAssets); - - // (uint256 totalSupplyAssets,, uint256 totalBorrowAssets,) = morpho.expectedMarketAssetss(marketParams); - // uint256 borrowTrue = irm.borrowRate(marketParams, market); - // uint256 utilization = totalBorrowAssets == 0 ? 0 : totalBorrowAssets.wDivUp(totalSupplyAssets); - - // uint256 supplyTrue = borrowTrue.wMulDown(1 ether - market.fee).wMulDown(utilization); - // uint256 supplyToTest = snippets.supplyAPRMarket(marketParams, market); - // assertEq(supplyTrue, 0, "Diff in snippets vs integration supplyAPR test"); - // assertEq(supplyToTest, 0, "Diff in snippets vs integration supplyAPR test"); - // } - - // TODO Implement the TEST SUPPLY APR Function - // function testSupplyAPRMarket(MarketParams memory marketParams, Market memory market) public { - // vm.assume(market.totalBorrowAssets > 0); - // vm.assume(market.fee < 1 ether); - // vm.assume(market.totalSupplyAssets >= market.totalBorrowAssets); - - // (uint256 totalSupplyAssets,, uint256 totalBorrowAssets,) = morpho.expectedMarketAssetss(marketParams); - // uint256 borrowTrue = irm.borrowRate(marketParams, market); - // assertGt(borrowTrue, 0, "intermediary test"); - // uint256 utilization = totalBorrowAssets == 0 ? 0 : totalBorrowAssets.wDivUp(totalSupplyAssets); - // assertGt(utilization, 0, "intermediary test"); - // uint256 supplyTrue = borrowTrue.wMulDown(1 ether - market.fee).wMulDown(utilization); - // // assertGt(supplyTrue, 0, "intermediary test"); - // // uint256 supplyToTest = snippets.supplyAPRMarket(marketParams, market); - - // // assertEq(supplyTrue, supplyToTest, "Diff in snippets vs integration supplyAPR test"); - // } - - // TODO Implement the TEST SUPPLY APR Vault Function + function testSupplyAPR0(Market memory market) public { + vm.assume(market.totalBorrowAssets == 0); + vm.assume(market.lastUpdate > 0); + vm.assume(market.fee < 1 ether); + vm.assume(market.totalSupplyAssets >= market.totalBorrowAssets); + MarketParams memory marketParams = allMarkets[0]; + (uint256 totalSupplyAssets,, uint256 totalBorrowAssets,) = morpho.expectedMarketBalances(marketParams); + uint256 borrowTrue = irm.borrowRateView(marketParams, market); + uint256 utilization = totalBorrowAssets == 0 ? 0 : totalBorrowAssets.wDivUp(totalSupplyAssets); + + uint256 supplyTrue = borrowTrue.wMulDown(1 ether - market.fee).wMulDown(utilization); + uint256 supplyToTest = snippets.supplyAPRMarket(marketParams, market); + assertEq(utilization, 0, "Diff in snippets vs integration supplyAPR test"); + assertEq(supplyTrue, 0, "Diff in snippets vs integration supplyAPR test"); + assertEq(supplyToTest, 0, "Diff in snippets vs integration supplyAPR test"); + } + + function testSupplyAPRMarket(Market memory market) public { + vm.assume(market.totalBorrowAssets > 0); + vm.assume(market.totalBorrowShares > 0); + vm.assume(market.totalSupplyAssets > 0); + vm.assume(market.totalSupplyShares > 0); + vm.assume(market.fee < 1 ether); + vm.assume(market.totalSupplyAssets >= market.totalBorrowAssets); + + MarketParams memory marketParams = allMarkets[0]; + (uint256 totalSupplyAssets,, uint256 totalBorrowAssets,) = morpho.expectedMarketBalances(marketParams); + + uint256 borrowTrue = irm.borrowRateView(marketParams, market); + uint256 utilization = totalBorrowAssets == 0 ? 0 : totalBorrowAssets.wDivUp(totalSupplyAssets); + uint256 supplyTrue = borrowTrue.wMulDown(1 ether - market.fee).wMulDown(utilization); + + uint256 supplyToTest = snippets.supplyAPRMarket(marketParams, market); + assertGt(supplyTrue, 0, "supply rate ==0"); + assertEq(supplyTrue, supplyToTest, "Diff in snippets vs integration supplyAPR test"); + } + + // TODO: enhance the test + function testSupplyAPRVault(uint256 deposited) public { + // set 2 suppliers and 1 borrower + uint256 firstDeposit = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS / 2); + uint256 secondDeposit = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS / 2); + _setCap(allMarkets[0], firstDeposit); + _setCap(allMarkets[1], secondDeposit); + + Id[] memory supplyQueue = new Id[](2); + supplyQueue[0] = allMarkets[0].id(); + supplyQueue[1] = allMarkets[1].id(); + + vm.prank(ALLOCATOR); + vault.setSupplyQueue(supplyQueue); + loanToken.setBalance(SUPPLIER, firstDeposit); + + vm.prank(SUPPLIER); + vault.deposit(firstDeposit, ONBEHALF); + + uint256 snippetTotalAsset = snippets.totalDepositVault(); + + assertEq(firstDeposit, snippetTotalAsset, "lastTotalAssets"); + + loanToken.setBalance(SUPPLIER, secondDeposit); + vm.prank(SUPPLIER); + vault.deposit(secondDeposit, ONBEHALF); + + uint256 snippetTotalAsset2 = snippets.totalDepositVault(); + assertEq(firstDeposit + secondDeposit, snippetTotalAsset2, "lastTotalAssets2"); + + collateralToken.setBalance(BORROWER, type(uint256).max); + vm.startPrank(BORROWER); + morpho.supplyCollateral(allMarkets[0], MAX_TEST_ASSETS, BORROWER, hex""); + morpho.borrow(allMarkets[0], firstDeposit, 0, BORROWER, BORROWER); + vm.stopPrank(); + + // // in the current state: borrower borrowed some liquidity in market 0 + + collateralToken.setBalance(BORROWER, type(uint256).max - MAX_TEST_ASSETS); + vm.startPrank(BORROWER); + morpho.supplyCollateral(allMarkets[1], MAX_TEST_ASSETS, BORROWER, hex""); + morpho.borrow(allMarkets[1], secondDeposit / 4, 0, BORROWER, BORROWER); + vm.stopPrank(); + + // in the current state: borrower borrowed some liquidity in market 1 as well, up to 1/4 of the liquidity + + uint256 avgSupplyRateSnippets = snippets.supplyAPRVault(); + assertGt(avgSupplyRateSnippets, 0, "avgSupplyRateSnippets ==0"); + + // market 0: utilization 100% -> firstDeposit*50% + secondDeposit * (50%/4) (only a quarter is borrowed) + _setFee(0); + + Id id0 = Id(allMarkets[0].id()); + + Id id1 = Id(allMarkets[1].id()); + Market memory market0 = morpho.market(id0); + Market memory market1 = morpho.market(id1); + + uint256 rateMarket0 = snippets.supplyAPRMarket(allMarkets[0], market0); + uint256 rateMarket1 = snippets.supplyAPRMarket(allMarkets[1], market1); + assertGt(rateMarket0, 0, "supply rate ==0"); + assertGt(rateMarket1, 0, "supply rate ==0"); + + uint256 avgRateNum = rateMarket0.wMulDown(firstDeposit) + rateMarket1.wMulDown(secondDeposit); + // uint256 totalDeposited = + // uint256 avgRate = avgRateNum.wDivDown(firstDeposit+secondDeposit) + uint256 avgRate = (firstDeposit + secondDeposit) == 0 ? 0 : avgRateNum.wDivUp(firstDeposit + secondDeposit); + assertGt(avgRate, 0, "supply rate ==0"); + assertEq(avgSupplyRateSnippets, avgRate, "avgSupplyRateSnippets ==0"); + } // MANAGING FUNCTION