Skip to content

Commit

Permalink
Merge pull request #30 from morpho-labs/test/deposit-withdraw-tests
Browse files Browse the repository at this point in the history
Test/deposit withdraw tests
  • Loading branch information
MerlinEgalite authored Sep 12, 2023
2 parents 2e1c7cc + 6df4999 commit d14f4c3
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 27 deletions.
6 changes: 3 additions & 3 deletions contracts/SupplyVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ contract SupplyVault is ERC4626, Ownable2Step, ISupplyVault {
for (uint256 i; i < length; ++i) {
Id id = supplyAllocationOrder[i];

MarketParams memory marketParams = _config.at(_config.getMarket(id).rank);
MarketParams memory marketParams = _config.at(_config.getMarket(id).rank - 1);
uint256 cap = marketCap(id);
uint256 toDeposit = assets;

Expand Down Expand Up @@ -425,9 +425,9 @@ contract SupplyVault is ERC4626, Ownable2Step, ISupplyVault {
view
returns (MarketParams memory marketParams, uint256 withdrawable)
{
marketParams = _config.at(_config.getMarket(id).rank);
marketParams = _config.at(_config.getMarket(id).rank - 1);
(uint256 totalSupply,, uint256 totalBorrow,) = MORPHO.expectedMarketBalances(marketParams);
uint256 available = totalBorrow - totalSupply;
uint256 available = totalSupply - totalBorrow;
withdrawable = UtilsLib.min(available, assets);
}

Expand Down
2 changes: 1 addition & 1 deletion lib/morpho-blue
206 changes: 206 additions & 0 deletions test/forge/ERC4626Test.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import "./helpers/BaseTest.sol";

contract ERC4626Test is BaseTest {
using MorphoBalancesLib for IMorpho;
using MarketParamsLib for MarketParams;

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

_submitAndEnableMarket(allMarkets[0], CAP);
}

function testMint(uint256 assets) public {
assets = bound(assets, MIN_TEST_ASSETS, MAX_TEST_ASSETS);

uint256 shares = vault.convertToShares(assets);

borrowableToken.setBalance(SUPPLIER, assets);

vm.prank(SUPPLIER);
uint256 deposited = vault.mint(shares, ONBEHALF);

uint256 totalBalanceAfter = morpho.expectedSupplyBalance(allMarkets[0], address(vault));

assertGt(deposited, 0, "deposited");
assertEq(vault.balanceOf(ONBEHALF), shares, "balanceOf(ONBEHALF)");
assertEq(morpho.expectedSupplyBalance(allMarkets[0], address(vault)), assets, "expectedSupplyBalance(vault)");
}

function testDeposit(uint256 assets) public {
assets = bound(assets, MIN_TEST_ASSETS, MAX_TEST_ASSETS);

borrowableToken.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.expectedSupplyBalance(allMarkets[0], address(vault)), assets, "expectedSupplyBalance(vault)");
}

function testRedeemTooMuch(uint256 deposited, uint256 shares) public {
deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS);

borrowableToken.setBalance(SUPPLIER, deposited);

vm.prank(SUPPLIER);
uint256 minted = vault.deposit(deposited, ONBEHALF);

shares = bound(shares, minted + 1, type(uint256).max);

vm.prank(SUPPLIER);
vm.expectRevert(bytes(ErrorsLib.WITHDRAW_ORDER_FAILED));
vault.redeem(shares, RECEIVER, ONBEHALF);
}

function testWithdrawAll(uint256 assets) public {
assets = bound(assets, MIN_TEST_ASSETS, MAX_TEST_ASSETS);

borrowableToken.setBalance(SUPPLIER, assets);

vm.prank(SUPPLIER);
uint256 minted = vault.deposit(assets, ONBEHALF);

assertEq(vault.maxWithdraw(ONBEHALF), assets, "maxWithdraw(ONBEHALF)");

vm.prank(ONBEHALF);
uint256 shares = vault.withdraw(assets, RECEIVER, ONBEHALF);

assertEq(shares, minted, "shares");
assertEq(vault.balanceOf(ONBEHALF), 0, "balanceOf(ONBEHALF)");
assertEq(borrowableToken.balanceOf(RECEIVER), assets, "borrowableToken.balanceOf(RECEIVER)");
assertEq(morpho.expectedSupplyBalance(allMarkets[0], address(vault)), 0, "expectedSupplyBalance(vault)");
}

function testRedeemAll(uint256 deposited) public {
deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS);

borrowableToken.setBalance(SUPPLIER, deposited);

vm.prank(SUPPLIER);
uint256 minted = vault.deposit(deposited, ONBEHALF);

assertEq(vault.maxRedeem(ONBEHALF), minted, "maxRedeem(ONBEHALF)");

vm.prank(ONBEHALF);
uint256 assets = vault.redeem(minted, RECEIVER, ONBEHALF);

assertEq(assets, deposited, "assets");
assertEq(vault.balanceOf(ONBEHALF), 0, "balanceOf(ONBEHALF)");
assertEq(borrowableToken.balanceOf(RECEIVER), deposited, "borrowableToken.balanceOf(RECEIVER)");
assertEq(morpho.expectedSupplyBalance(allMarkets[0], address(vault)), 0, "expectedSupplyBalance(vault)");
}

function testRedeemNotDeposited(uint256 deposited) public {
deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS);

borrowableToken.setBalance(SUPPLIER, deposited);

vm.prank(SUPPLIER);
uint256 shares = vault.deposit(deposited, ONBEHALF);

vm.prank(SUPPLIER);
vm.expectRevert("ERC20: burn amount exceeds balance");
vault.redeem(shares, SUPPLIER, SUPPLIER);
}

function testRedeemNotApproved(uint256 deposited) public {
deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS);

borrowableToken.setBalance(SUPPLIER, deposited);

vm.prank(SUPPLIER);
uint256 shares = vault.deposit(deposited, ONBEHALF);

vm.prank(RECEIVER);
vm.expectRevert("ERC20: insufficient allowance");
vault.redeem(shares, RECEIVER, ONBEHALF);
}

function testWithdrawNotApproved(uint256 assets) public {
assets = bound(assets, MIN_TEST_ASSETS, MAX_TEST_ASSETS);

borrowableToken.setBalance(SUPPLIER, assets);

vm.prank(SUPPLIER);
vault.deposit(assets, ONBEHALF);

vm.prank(RECEIVER);
vm.expectRevert("ERC20: insufficient allowance");
vault.withdraw(assets, RECEIVER, ONBEHALF);
}

function testTransferFrom(uint256 deposited, uint256 amount) public {
deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS);

borrowableToken.setBalance(SUPPLIER, deposited);

vm.prank(SUPPLIER);
uint256 shares = vault.deposit(deposited, ONBEHALF);

amount = bound(amount, 0, shares);

vm.prank(ONBEHALF);
vault.approve(SUPPLIER, amount);

vm.prank(SUPPLIER);
vault.transferFrom(ONBEHALF, RECEIVER, amount);

assertEq(vault.balanceOf(ONBEHALF), deposited - amount, "balanceOf(ONBEHALF)");
assertEq(vault.balanceOf(RECEIVER), amount, "balanceOf(RECEIVER)");
assertEq(vault.balanceOf(SUPPLIER), 0, "balanceOf(SUPPLIER)");
}

function testTransferFromNotApproved(uint256 deposited, uint256 amount) public {
deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS);

borrowableToken.setBalance(SUPPLIER, deposited);

vm.prank(SUPPLIER);
uint256 shares = vault.deposit(deposited, ONBEHALF);

amount = bound(amount, 0, shares);

vm.prank(SUPPLIER);
vm.expectRevert("ERC20: insufficient allowance");
vault.transferFrom(ONBEHALF, RECEIVER, shares);
}

function testWithdrawTooMuch(uint256 deposited, uint256 assets) public {
deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS);

borrowableToken.setBalance(SUPPLIER, deposited);

vm.prank(SUPPLIER);
vault.deposit(deposited, ONBEHALF);

assets = bound(assets, deposited + 1, type(uint256).max);

vm.prank(SUPPLIER);
vm.expectRevert(bytes(ErrorsLib.WITHDRAW_ORDER_FAILED));
vault.withdraw(assets, RECEIVER, ONBEHALF);
}

function testTransfer(uint256 deposited, uint256 amount) public {
deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS);

borrowableToken.setBalance(SUPPLIER, deposited);

vm.prank(SUPPLIER);
uint256 shares = vault.deposit(deposited, ONBEHALF);

amount = bound(amount, 0, shares);

vm.prank(ONBEHALF);
vault.transfer(RECEIVER, amount);

assertEq(vault.balanceOf(SUPPLIER), 0, "balanceOf(SUPPLIER)");
assertEq(vault.balanceOf(ONBEHALF), deposited - amount, "balanceOf(ONBEHALF)");
assertEq(vault.balanceOf(RECEIVER), amount, "balanceOf(RECEIVER)");
}
}
54 changes: 31 additions & 23 deletions test/forge/helpers/BaseTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ contract BaseTest is Test {
using stdJson for string;

uint256 internal constant BLOCK_TIME = 12;
uint256 internal constant MIN_TEST_AMOUNT = 100;
uint256 internal constant MAX_TEST_AMOUNT = 1e28;
uint256 internal constant MIN_TEST_ASSETS = 100;
uint256 internal constant MAX_TEST_ASSETS = 1e28;
uint256 internal constant MIN_TEST_LLTV = 0.01 ether;
uint256 internal constant MAX_TEST_LLTV = 0.99 ether;
uint256 internal constant NB_MARKETS = 10;
Expand Down Expand Up @@ -77,6 +77,7 @@ contract BaseTest is Test {
vm.label(address(collateralToken), "Collateral");

oracle = new OracleMock();
vm.label(address(oracle), "Oracle");

oracle.setPrice(ORACLE_PRICE_SCALE);

Expand All @@ -87,49 +88,56 @@ contract BaseTest is Test {
morpho.setFeeRecipient(MORPHO_FEE_RECIPIENT);
vm.stopPrank();

vm.startPrank(OWNER);
vault = new SupplyVault(address(morpho), TIMELOCK, IERC20(address(borrowableToken)), "MetaMorpho Vault", "MMV");

vault.setIsRiskManager(RISK_MANAGER, true);
vault.setIsAllocator(ALLOCATOR, true);
vm.stopPrank();

for (uint256 i; i < NB_MARKETS; ++i) {
uint256 lltv = 0.8 ether / (i + 1);

MarketParams memory marketParams = MarketParams({
borrowableToken: address(borrowableToken),
collateralToken: address(collateralToken),
oracle: address(oracle),
irm: address(irm),
lltv: lltv
});

vm.startPrank(MORPHO_OWNER);
morpho.enableLltv(lltv);
morpho.createMarket(marketParams);
vm.stopPrank();

allMarkets.push(marketParams);
}

borrowableToken.approve(address(vault), type(uint256).max);
collateralToken.approve(address(vault), type(uint256).max);

vm.startPrank(SUPPLIER);
borrowableToken.approve(address(vault), type(uint256).max);
collateralToken.approve(address(vault), type(uint256).max);
borrowableToken.approve(address(morpho), type(uint256).max);
collateralToken.approve(address(morpho), type(uint256).max);
vm.stopPrank();

vm.startPrank(BORROWER);
borrowableToken.approve(address(morpho), type(uint256).max);
collateralToken.approve(address(morpho), type(uint256).max);
vm.stopPrank();

vm.startPrank(REPAYER);
borrowableToken.approve(address(morpho), type(uint256).max);
collateralToken.approve(address(morpho), type(uint256).max);
vm.stopPrank();

vm.startPrank(ONBEHALF);
borrowableToken.approve(address(vault), type(uint256).max);
collateralToken.approve(address(vault), type(uint256).max);
vm.stopPrank();

vm.startPrank(OWNER);
vault = new SupplyVault(address(morpho), TIMELOCK, IERC20(address(borrowableToken)), "MetaMorpho Vault", "MMV");

vault.setIsRiskManager(RISK_MANAGER, true);
vault.setIsAllocator(ALLOCATOR, true);
vm.stopPrank();

for (uint256 i; i < NB_MARKETS; ++i) {
uint256 lltv = 0.8 ether / (i + 1);

MarketParams memory marketParams =
MarketParams(address(borrowableToken), address(collateralToken), address(oracle), address(irm), lltv);

vm.startPrank(MORPHO_OWNER);
morpho.enableLltv(lltv);
morpho.createMarket(marketParams);
vm.stopPrank();

allMarkets.push(marketParams);
}
}

function _addrFromHashedString(string memory name) internal returns (address addr) {
Expand Down

0 comments on commit d14f4c3

Please sign in to comment.