From 8e5853ab8ef5d21aefef0890bd274d0e0e180382 Mon Sep 17 00:00:00 2001 From: Jean-Grimal Date: Tue, 28 Nov 2023 17:31:21 +0100 Subject: [PATCH] feat: review and refactor --- src/metamorpho/MetamorphoSnippets.sol | 129 +++++++++++------ .../metamorpho/TestMetamorphoSnippets.sol | 132 +++++++----------- 2 files changed, 139 insertions(+), 122 deletions(-) diff --git a/src/metamorpho/MetamorphoSnippets.sol b/src/metamorpho/MetamorphoSnippets.sol index ab0b1eb..6f8de38 100644 --- a/src/metamorpho/MetamorphoSnippets.sol +++ b/src/metamorpho/MetamorphoSnippets.sol @@ -15,58 +15,76 @@ import {ERC20} from "@openzeppelin/token/ERC20/ERC20.sol"; contract MetamorphoSnippets { IMorpho public immutable morpho; - IMetaMorpho public immutable vault; using MathLib for uint256; using Math for uint256; using MarketParamsLib for MarketParams; using MorphoBalancesLib for IMorpho; - constructor(address vaultAddress, address morphoAddress) { + constructor(address morphoAddress) { morpho = IMorpho(morphoAddress); - vault = IMetaMorpho(vaultAddress); } // --- VIEW FUNCTIONS --- - /// @dev note that it corresponds to when the fee was last accrued. - function totalDepositVault() public view returns (uint256 totalAssets) { - totalAssets = vault.lastTotalAssets(); + /// @notice Returns the total assets deposited into a metamorpho `vault`. + /// @dev it doesn't take into account the fees accrued since the last update. + /// @param vault The address of the metamorpho vault. + function totalDepositVault(address vault) public view returns (uint256 totalAssets) { + totalAssets = IMetaMorpho(vault).lastTotalAssets(); } - function vaultAssetsInMarket(MarketParams memory marketParams) public view returns (uint256 vaultAmount) { - vaultAmount = morpho.expectedSupplyAssets(marketParams, address(vault)); + /// @notice Returns the total assets supplied into a specific morpho blue market by a metamorpho `vault`. + /// @param vault The address of the metamorpho vault. + /// @param marketParams The morpho blue market. + function vaultAssetsInMarket(address vault, MarketParams memory marketParams) + public + view + returns (uint256 vaultAmount) + { + vaultAmount = morpho.expectedSupplyAssets(marketParams, vault); } - function totalSharesUserVault(address user) public view returns (uint256 totalSharesUser) { - totalSharesUser = vault.balanceOf(user); + /// @notice Returns the total shares balance of a `user` on a metamorpho `vault`. + /// @param vault The address of the metamorpho vault. + /// @param user The address of the user. + function totalSharesUserVault(address vault, address user) public view returns (uint256 totalSharesUser) { + totalSharesUser = IMetaMorpho(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(); + /// @notice Returns the supply queue a metamorpho `vault`. + /// @param vault The address of the metamorpho vault. + function supplyQueueVault(address vault) public view returns (Id[] memory supplyQueueList) { + uint256 queueLength = IMetaMorpho(vault).supplyQueueLength(); supplyQueueList = new Id[](queueLength); for (uint256 i; i < queueLength; ++i) { - supplyQueueList[i] = vault.supplyQueue(i); + supplyQueueList[i] = IMetaMorpho(vault).supplyQueue(i); } 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(); + /// @notice Returns the withdraw queue a metamorpho `vault`. + /// @param vault The address of the metamorpho vault. + function withdrawQueueVault(address vault) public view returns (Id[] memory withdrawQueueList) { + uint256 queueLength = IMetaMorpho(vault).supplyQueueLength(); withdrawQueueList = new Id[](queueLength); for (uint256 i; i < queueLength; ++i) { - withdrawQueueList[i] = vault.withdrawQueue(i); + withdrawQueueList[i] = IMetaMorpho(vault).withdrawQueue(i); } return withdrawQueueList; } - function capMarket(MarketParams memory marketParams) public view returns (uint192 cap) { + /// @notice Returns the supply cap of a market on a metamorpho `vault`. + /// @param vault The address of the metamorpho vault. + /// @param marketParams The morpho blue market. + function capMarket(address vault, MarketParams memory marketParams) public view returns (uint192 cap) { Id id = marketParams.id(); - cap = vault.config(id).cap; + cap = IMetaMorpho(vault).config(id).cap; } + /// @notice Returns the current APR (Annual Percentage Rate) of a morpho blue market. + /// @param marketParams The morpho blue market parameters. + /// @param market The morpho blue market state. function supplyAPRMarket(MarketParams memory marketParams, Market memory market) public view @@ -83,18 +101,23 @@ contract MetamorphoSnippets { supplyRate = borrowRate.wMulDown(1 ether - market.fee).wMulDown(utilization); } - function supplyAPRVault() public view returns (uint256 avgSupplyRate) { + /// @notice Returns the current APR (Annual Percentage Rate) of a metamorpho vault. + /// @dev It is computed as the sum of all APR of enabled markets weighted by the supply on these markets. + /// @param vault The address of the metamorpho vault. + function supplyAPRVault(address vault) public view returns (uint256 avgSupplyRate) { uint256 ratio; - uint256 queueLength = vault.withdrawQueueLength(); - uint256 totalAmount = totalDepositVault(); + uint256 queueLength = IMetaMorpho(vault).withdrawQueueLength(); + + uint256 totalAmount = totalDepositVault(vault); for (uint256 i; i < queueLength; ++i) { - Id idMarket = vault.withdrawQueue(i); + Id idMarket = IMetaMorpho(vault).withdrawQueue(i); + MarketParams memory marketParams = morpho.idToMarketParams(idMarket); Market memory market = morpho.market(idMarket); uint256 currentSupplyAPR = supplyAPRMarket(marketParams, market); - uint256 vaultAsset = vaultAssetsInMarket(marketParams); + uint256 vaultAsset = vaultAssetsInMarket(vault, marketParams); ratio += currentSupplyAPR.wMulDown(vaultAsset); } @@ -103,28 +126,50 @@ contract MetamorphoSnippets { // // --- MANAGING FUNCTIONS --- - // deposit in the vault a nb of asset - function depositInVault(uint256 assets, address onBehalf) public returns (uint256 shares) { - shares = vault.deposit(assets, onBehalf); + /// @notice Deposit `assets` into the `vault` on behalf of `onBehalf`. + /// @dev Sender must approve the snippets contract to manage his tokens before the call. + /// @param vault The address of the metamorpho vault. + /// @param assets the amount to deposit. + /// @param onBehalf The address that will own the increased deposit position. + function depositInVault(address vault, uint256 assets, address onBehalf) public returns (uint256 shares) { + ERC20(IMetaMorpho(vault).asset()).transferFrom(msg.sender, address(this), assets); + + _approveMaxVault(vault); + + shares = IMetaMorpho(vault).deposit(assets, onBehalf); + } + + /// @notice Withdraws `assets` from the `vault` on behalf of the sender, and sends them to `receiver`. + /// @dev Sender must approve the snippets contract to manage his tokens before the call. + /// @param vault The address of the metamorpho vault. + /// @param assets the amount to withdraw. + /// @param receiver The address that will receive the withdrawn assets. + function withdrawFromVaultAmount(address vault, uint256 assets, address receiver) + public + returns (uint256 redeemed) + { + redeemed = IMetaMorpho(vault).withdraw(assets, receiver, msg.sender); } - // withdraw from the vault a nb of asset - function withdrawFromVaultAmount(uint256 assets, address onBehalf) public returns (uint256 redeemed) { - address receiver = onBehalf; - redeemed = vault.withdraw(assets, receiver, onBehalf); + /// @notice Withdraws the whole sender's position from the `vault`, and sends the withdrawn amount to `receiver`. + /// @param vault The address of the metamorpho vault. + /// @param receiver The address that will receive the withdrawn assets. + function withdrawFromVaultAll(address vault, address receiver) public returns (uint256 redeemed) { + uint256 assets = IMetaMorpho(vault).maxWithdraw(msg.sender); + redeemed = IMetaMorpho(vault).withdraw(assets, receiver, msg.sender); } - // maxWithdraw from the vault - function withdrawFromVaultAll(address onBehalf) public returns (uint256 redeemed) { - address receiver = onBehalf; - uint256 assets = vault.maxWithdraw(address(this)); - redeemed = vault.withdraw(assets, receiver, onBehalf); + /// @notice Redeems the whole sender's position from the `vault`, and sends the withdrawn amount to `receiver`. + /// @param vault The address of the metamorpho vault. + /// @param receiver The address that will receive the withdrawn assets. + function redeemAllFromVault(address vault, address receiver) public returns (uint256 redeemed) { + uint256 maxToRedeem = IMetaMorpho(vault).maxRedeem(msg.sender); + redeemed = IMetaMorpho(vault).redeem(maxToRedeem, receiver, msg.sender); } - // maxRedeem from the vault - function redeemAllFromVault(address onBehalf) public returns (uint256 redeemed) { - address receiver = onBehalf; - uint256 maxToRedeem = vault.maxRedeem(address(this)); - redeemed = vault.redeem(maxToRedeem, receiver, onBehalf); + function _approveMaxVault(address vault) internal { + if (ERC20(IMetaMorpho(vault).asset()).allowance(address(this), vault) == 0) { + ERC20(IMetaMorpho(vault).asset()).approve(vault, type(uint256).max); + } } } diff --git a/test/forge/metamorpho/TestMetamorphoSnippets.sol b/test/forge/metamorpho/TestMetamorphoSnippets.sol index 1a167aa..9ab30e8 100644 --- a/test/forge/metamorpho/TestMetamorphoSnippets.sol +++ b/test/forge/metamorpho/TestMetamorphoSnippets.sol @@ -17,32 +17,33 @@ contract TestIntegrationSnippets is IntegrationTest { function setUp() public virtual override { super.setUp(); - snippets = new MetamorphoSnippets(address(vault), address(morpho)); + snippets = new MetamorphoSnippets(address(morpho)); _setCap(allMarkets[0], CAP); _sortSupplyQueueIdleLast(); + + vm.startPrank(SUPPLIER); + ERC20(vault.asset()).approve(address(snippets), type(uint256).max); + vault.approve(address(snippets), type(uint256).max); + vm.stopPrank(); } - function testTotalDepositVault(uint256 deposited) public { - uint256 firstDeposit = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS / 2); - uint256 secondDeposit = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS / 2); + function testTotalDepositVault(uint256 firstDeposit, uint256 secondDeposit) public { + firstDeposit = bound(firstDeposit, MIN_TEST_ASSETS, MAX_TEST_ASSETS / 2); + secondDeposit = bound(secondDeposit, MIN_TEST_ASSETS, MAX_TEST_ASSETS / 2); loanToken.setBalance(SUPPLIER, firstDeposit); vm.prank(SUPPLIER); vault.deposit(firstDeposit, ONBEHALF); - uint256 snippetTotalAsset = snippets.totalDepositVault(); - - assertEq(firstDeposit, snippetTotalAsset, "lastTotalAssets"); + assertEq(snippets.totalDepositVault(address(vault)), firstDeposit, "lastTotalAssets"); loanToken.setBalance(SUPPLIER, secondDeposit); vm.prank(SUPPLIER); vault.deposit(secondDeposit, ONBEHALF); - uint256 snippetTotalAsset2 = snippets.totalDepositVault(); - - assertEq(firstDeposit + secondDeposit, snippetTotalAsset2, "lastTotalAssets2"); + assertEq(snippets.totalDepositVault(address(vault)), firstDeposit + secondDeposit, "lastTotalAssets2"); } function testVaultAssetsInMarket(uint256 assets) public { @@ -56,9 +57,7 @@ contract TestIntegrationSnippets is IntegrationTest { assertGt(shares, 0, "shares"); assertEq(vault.balanceOf(ONBEHALF), shares, "balanceOf(ONBEHALF)"); assertEq(morpho.expectedSupplyAssets(allMarkets[0], address(vault)), assets, "expectedSupplyAssets(vault)"); - - uint256 vaultAmount = snippets.vaultAssetsInMarket(allMarkets[0]); - assertEq(assets, vaultAmount, "expectedSupplyAssets(vault)"); + assertEq(snippets.vaultAssetsInMarket(address(vault), allMarkets[0]), assets, "expectedSupplyAssets(vault)"); } function testTotalSharesUserVault(uint256 deposited) public { @@ -69,9 +68,7 @@ contract TestIntegrationSnippets is IntegrationTest { uint256 shares = vault.deposit(deposited, ONBEHALF); assertEq(vault.balanceOf(ONBEHALF), shares, "balanceOf(ONBEHALF)"); - - uint256 snippetUserShares = snippets.totalSharesUserVault(ONBEHALF); - assertEq(shares, snippetUserShares, "UserShares"); + assertEq(snippets.totalSharesUserVault(address(vault), ONBEHALF), shares, "UserShares"); } function testSupplyQueueVault() public { @@ -86,7 +83,7 @@ contract TestIntegrationSnippets is IntegrationTest { 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(); + Id[] memory supplyQueueList = snippets.supplyQueueVault(address(vault)); assertEq(Id.unwrap(supplyQueueList[0]), Id.unwrap(allMarkets[1].id())); assertEq(Id.unwrap(supplyQueueList[1]), Id.unwrap(allMarkets[2].id())); } @@ -116,7 +113,7 @@ contract TestIntegrationSnippets is IntegrationTest { 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(); + Id[] memory withdrawQueueList = snippets.withdrawQueueVault(address(vault)); assertEq(Id.unwrap(withdrawQueueList[0]), Id.unwrap(expectedWithdrawQueue[0])); assertEq(Id.unwrap(withdrawQueueList[1]), Id.unwrap(expectedWithdrawQueue[1])); @@ -124,9 +121,8 @@ contract TestIntegrationSnippets is IntegrationTest { function testCapMarket(MarketParams memory marketParams) public { Id idMarket = marketParams.id(); - uint192 cap = vault.config(idMarket).cap; - uint192 snippetCap = snippets.capMarket(marketParams); - assertEq(cap, snippetCap, "cap per market"); + + assertEq(vault.config(idMarket).cap, snippets.capMarket(address(vault), marketParams), "cap per market"); } function testSupplyAPR0(Market memory market) public { @@ -134,16 +130,20 @@ contract TestIntegrationSnippets is IntegrationTest { 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"); + assertEq( + borrowTrue.wMulDown(1 ether - market.fee).wMulDown(utilization), + 0, + "Diff in snippets vs integration supplyAPR test" + ); + assertEq(snippets.supplyAPRMarket(marketParams, market), 0, "Diff in snippets vs integration supplyAPR test"); } function testSupplyAPRMarket(Market memory market) public { @@ -192,16 +192,13 @@ contract TestIntegrationSnippets is IntegrationTest { vm.prank(SUPPLIER); vault.deposit(firstDeposit, ONBEHALF); - uint256 snippetTotalAsset = snippets.totalDepositVault(); - - assertEq(firstDeposit, snippetTotalAsset, "lastTotalAssets"); + assertEq(snippets.totalDepositVault(address(vault)), firstDeposit, "lastTotalAssets"); loanToken.setBalance(SUPPLIER, secondDeposit); vm.prank(SUPPLIER); vault.deposit(secondDeposit, ONBEHALF); - uint256 snippetTotalAsset2 = snippets.totalDepositVault(); - assertEq(firstDeposit + secondDeposit, snippetTotalAsset2, "lastTotalAssets2"); + assertEq(snippets.totalDepositVault(address(vault)), firstDeposit + secondDeposit, "lastTotalAssets2"); collateralToken.setBalance(BORROWER, type(uint256).max); vm.startPrank(BORROWER); @@ -219,7 +216,7 @@ contract TestIntegrationSnippets is IntegrationTest { // in the current state: borrower borrowed some liquidity in market 1 as well, up to 1/4 of the liquidity - uint256 avgSupplyRateSnippets = snippets.supplyAPRVault(); + uint256 avgSupplyRateSnippets = snippets.supplyAPRVault(address(vault)); assertGt(avgSupplyRateSnippets, 0, "avgSupplyRateSnippets ==0"); // market 0: utilization 100% -> firstDeposit*50% + secondDeposit * (50%/4) (only a quarter is borrowed) @@ -249,85 +246,60 @@ contract TestIntegrationSnippets is IntegrationTest { function testDepositInVault(uint256 assets) public { assets = bound(assets, MIN_TEST_ASSETS, MAX_TEST_ASSETS); - loanToken.setBalance(address(snippets), assets); - - vm.startPrank(address(snippets)); - loanToken.approve(address(morpho), type(uint256).max); - collateralToken.approve(address(morpho), type(uint256).max); - loanToken.approve(address(vault), type(uint256).max); - collateralToken.approve(address(vault), type(uint256).max); - vm.stopPrank(); - - uint256 shares = snippets.depositInVault(assets, address(snippets)); + loanToken.setBalance(SUPPLIER, assets); + vm.prank(SUPPLIER); + uint256 shares = snippets.depositInVault(address(vault), assets, SUPPLIER); assertGt(shares, 0, "shares"); - assertEq(vault.balanceOf(address(snippets)), shares, "balanceOf(address(snippets))"); + assertEq(vault.balanceOf(SUPPLIER), shares, "balanceOf(SUPPLIER)"); } function testWithdrawFromVaultAmount(uint256 deposited, uint256 withdrawn) public { deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS); withdrawn = bound(withdrawn, 0, deposited); - loanToken.setBalance(address(snippets), deposited); - - vm.startPrank(address(snippets)); - loanToken.approve(address(morpho), type(uint256).max); - collateralToken.approve(address(morpho), type(uint256).max); - loanToken.approve(address(vault), type(uint256).max); - collateralToken.approve(address(vault), type(uint256).max); - - uint256 shares = vault.deposit(deposited, address(snippets)); - - uint256 redeemed = snippets.withdrawFromVaultAmount(withdrawn, address(snippets)); + loanToken.setBalance(SUPPLIER, deposited); + vm.startPrank(SUPPLIER); + uint256 shares = vault.deposit(deposited, SUPPLIER); + uint256 redeemed = snippets.withdrawFromVaultAmount(address(vault), withdrawn, SUPPLIER); vm.stopPrank(); - assertEq(vault.balanceOf(address(snippets)), shares - redeemed, "balanceOf(address(snippets))"); + assertEq(vault.balanceOf(SUPPLIER), shares - redeemed, "balanceOf(SUPPLIER)"); } function testWithdrawFromVaultAll(uint256 assets) public { assets = bound(assets, MIN_TEST_ASSETS, MAX_TEST_ASSETS); - loanToken.setBalance(address(snippets), assets); - - vm.startPrank(address(snippets)); - loanToken.approve(address(morpho), type(uint256).max); - collateralToken.approve(address(morpho), type(uint256).max); - loanToken.approve(address(vault), type(uint256).max); - collateralToken.approve(address(vault), type(uint256).max); - - uint256 minted = vault.deposit(assets, address(snippets)); + loanToken.setBalance(SUPPLIER, assets); + vm.startPrank(SUPPLIER); + uint256 minted = vault.deposit(assets, SUPPLIER); - assertEq(vault.maxWithdraw(address(snippets)), assets, "maxWithdraw(ONBEHALF)"); + assertEq(vault.maxWithdraw(SUPPLIER), assets, "maxWithdraw(SUPPLIER)"); - uint256 redeemed = snippets.withdrawFromVaultAll(address(snippets)); + uint256 redeemed = snippets.withdrawFromVaultAll(address(vault), SUPPLIER); vm.stopPrank(); assertEq(redeemed, minted, "shares"); - assertEq(vault.balanceOf(address(snippets)), 0, "balanceOf(address(snippets))"); - assertEq(loanToken.balanceOf(address(snippets)), assets, "loanToken.balanceOf(address(snippets))"); + assertEq(vault.balanceOf(SUPPLIER), 0, "balanceOf(SUPPLIER)"); + assertEq(loanToken.balanceOf(SUPPLIER), assets, "loanToken.balanceOf(SUPPLIER)"); assertEq(morpho.expectedSupplyAssets(allMarkets[0], address(vault)), 0, "expectedSupplyAssets(vault)"); } function testRedeemAllFromVault(uint256 deposited) public { deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS); - loanToken.setBalance(address(snippets), deposited); - vm.startPrank(address(snippets)); - loanToken.approve(address(morpho), type(uint256).max); - collateralToken.approve(address(morpho), type(uint256).max); - loanToken.approve(address(vault), type(uint256).max); - collateralToken.approve(address(vault), type(uint256).max); - - uint256 minted = vault.deposit(deposited, address(snippets)); - - assertEq(vault.maxRedeem(address(snippets)), minted, "maxRedeem(ONBEHALF)"); + loanToken.setBalance(SUPPLIER, deposited); + vm.startPrank(SUPPLIER); + uint256 minted = vault.deposit(deposited, SUPPLIER); - uint256 redeemed = snippets.redeemAllFromVault(address(snippets)); + assertEq(vault.maxRedeem(SUPPLIER), minted, "maxRedeem(SUPPLIER)"); + uint256 redeemed = snippets.redeemAllFromVault(address(vault), SUPPLIER); vm.stopPrank(); + assertEq(redeemed, deposited, "assets"); - assertEq(vault.balanceOf(address(snippets)), 0, "balanceOf(address(snippets))"); - assertEq(loanToken.balanceOf(address(snippets)), deposited, "loanToken.balanceOf(address(snippets))"); + assertEq(vault.balanceOf(SUPPLIER), 0, "balanceOf(SUPPLIER)"); + assertEq(loanToken.balanceOf(SUPPLIER), deposited, "loanToken.balanceOf(SUPPLIER)"); assertEq(morpho.expectedSupplyAssets(allMarkets[0], address(vault)), 0, "expectedSupplyAssets(vault)"); }