Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fee tests #33

Merged
merged 24 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bf387ca
test: draft
MerlinEgalite Sep 10, 2023
ebe58aa
test: add simple tests
MerlinEgalite Sep 11, 2023
7a35159
test: fee accrual
MerlinEgalite Sep 11, 2023
b1127f5
fix: prevent several updated of lastTotalAssets in a block
MerlinEgalite Sep 11, 2023
fb95a81
fix(vault): sync last total assets last
Rubilmax Sep 12, 2023
17e69bc
Merge branch 'test/deposit-withdraw-tests' of github.com:morpho-labs/…
Rubilmax Sep 12, 2023
fc73b4c
Merge branch 'test/fees' of github.com:morpho-labs/morpho-blue-metamo…
Rubilmax Sep 12, 2023
f058644
refactor(vault): rename sync to update
Rubilmax Sep 12, 2023
5d4be39
feat(vault): revert on fee recipient zero
Rubilmax Sep 12, 2023
46bea01
Merge pull request #38 from morpho-labs/fix/deposit-fee
MerlinEgalite Sep 12, 2023
6f33140
Merge branch 'main' of github.com:morpho-labs/morpho-blue-metamorpho …
Rubilmax Sep 13, 2023
93a737c
test(forge): update fee tests WIP
Rubilmax Sep 13, 2023
c9a1541
Merge pull request #43 from morpho-labs/feat/revert-zero-fee-recipient
Rubilmax Sep 13, 2023
5c44638
Merge branch 'main' of github.com:morpho-labs/morpho-blue-metamorpho …
Rubilmax Sep 20, 2023
6b3ef6e
Merge branch 'test/fees' of github.com:morpho-labs/morpho-blue-metamo…
Rubilmax Sep 20, 2023
d21657c
fix(metamorpho): cannot unset fee recipient if fee > 0
Rubilmax Sep 20, 2023
1e68e71
Merge branch 'fix/zero-fee-recipient' of github.com:morpho-labs/morph…
Rubilmax Sep 20, 2023
3650c46
test(fee): refactor fee tests
Rubilmax Sep 20, 2023
74bffda
refactor(test): move debt initialization to fee test
Rubilmax Sep 20, 2023
1c6cd2e
test(fee): fix base test
Rubilmax Sep 20, 2023
22da12a
refactor(test): rename variable
Rubilmax Sep 20, 2023
86348ec
Merge branch 'main' of github.com:morpho-labs/morpho-blue-metamorpho …
Rubilmax Sep 20, 2023
a6db0cd
refactor(fee-test): rename parameter
Rubilmax Sep 20, 2023
2bdc843
Merge branch 'main' of github.com:morpho-labs/morpho-blue-metamorpho …
Rubilmax Sep 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,6 @@ fs_permissions = [
{ access = "read", path = "./lib/morpho-blue/out/"}
]

[profile.default.fuzz]
runs = 32

[profile.default.invariant]
runs = 16
depth = 4

[profile.default.rpc_endpoints]
mainnet = "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}"
tenderly = "https://rpc.tenderly.co/fork/${TENDERLY_FORK_ID}"
Expand Down
1 change: 1 addition & 0 deletions src/MetaMorpho.sol
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ contract MetaMorpho is ERC4626, Ownable2Step, IMetaMorpho {

function setFeeRecipient(address newFeeRecipient) external onlyOwner {
require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);
require(newFeeRecipient != address(0) || fee == 0, ErrorsLib.ZERO_FEE_RECIPIENT);

// Accrue interest to the previous fee recipient set before changing it.
_updateLastTotalAssets(_accrueFee());
Expand Down
2 changes: 2 additions & 0 deletions src/libraries/ErrorsLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ library ErrorsLib {
string internal constant TIMELOCK_EXPIRATION_EXCEEDED = "timelock expiration exceeded";

string internal constant MAX_QUEUE_SIZE_EXCEEDED = "max queue size exceeded";

string internal constant ZERO_FEE_RECIPIENT = "fee recipient is zero";
}
14 changes: 8 additions & 6 deletions src/mocks/IrmMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ import {MathLib} from "@morpho-blue/libraries/MathLib.sol";
contract IrmMock is IIrm {
using MathLib for uint128;

function borrowRateView(MarketParams memory, Market memory market) public pure returns (uint256) {
uint256 utilization = market.totalBorrowAssets.wDivDown(market.totalSupplyAssets);
uint256 public apr;

// Divide by the number of seconds in a year.
// This is a very simple model where x% utilization corresponds to x% APR.
return utilization / 365 days;
function setApr(uint256 newApr) external {
apr = newApr;
}

function borrowRate(MarketParams memory marketParams, Market memory market) external pure returns (uint256) {
function borrowRateView(MarketParams memory, Market memory) public view returns (uint256) {
return apr / 365 days;
}

function borrowRate(MarketParams memory marketParams, Market memory market) external view returns (uint256) {
return borrowRateView(marketParams, market);
}
}
199 changes: 199 additions & 0 deletions test/forge/FeeTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import "./helpers/BaseTest.sol";

contract FeeTest is BaseTest {
using Math for uint256;
using MathLib for uint256;
using MarketParamsLib for MarketParams;

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

_setCap(allMarkets[0], CAP);
}

function _feeShares(uint256 totalAssetsBefore) internal view returns (uint256) {
uint256 totalAssetsAfter = vault.totalAssets();
uint256 interest = totalAssetsAfter - totalAssetsBefore;
uint256 feeAmount = interest.wMulDown(FEE);

return feeAmount.mulDiv(
vault.totalSupply() + 10 ** DECIMALS_OFFSET, totalAssetsAfter - feeAmount + 1, Math.Rounding.Down
);
}

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

borrowableToken.setBalance(SUPPLIER, deposited);

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

assertEq(vault.lastTotalAssets(), vault.totalAssets(), "lastTotalAssets");
}

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

borrowableToken.setBalance(SUPPLIER, deposited);

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

vm.prank(ONBEHALF);
vault.withdraw(withdrawn, RECEIVER, ONBEHALF);

assertEq(vault.balanceOf(FEE_RECIPIENT), 0, "vault.balanceOf(FEE_RECIPIENT)");
}

function testDepositAccrueFee(uint256 deposited, uint256 assets, uint256 blocks) public {
deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS);
assets = bound(assets, MIN_TEST_ASSETS, MAX_TEST_ASSETS);
Rubilmax marked this conversation as resolved.
Show resolved Hide resolved
blocks = _boundBlocks(blocks);

borrowableToken.setBalance(SUPPLIER, deposited);

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

uint256 totalAssetsBefore = vault.totalAssets();

_forward(blocks);

uint256 feeShares = _feeShares(totalAssetsBefore);

borrowableToken.setBalance(SUPPLIER, assets);

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

assertEq(vault.lastTotalAssets(), vault.totalAssets(), "lastTotalAssets");
assertEq(vault.balanceOf(FEE_RECIPIENT), feeShares, "vault.balanceOf(FEE_RECIPIENT)");
}

function testMintAccrueFee(uint256 deposited, uint256 assets, uint256 blocks) public {
deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS);
assets = bound(assets, MIN_TEST_ASSETS, MAX_TEST_ASSETS);
blocks = _boundBlocks(blocks);

borrowableToken.setBalance(SUPPLIER, deposited);

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

uint256 totalAssetsBefore = vault.totalAssets();

_forward(blocks);

uint256 feeShares = _feeShares(totalAssetsBefore);

uint256 shares = vault.convertToShares(assets);

borrowableToken.setBalance(SUPPLIER, assets);

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

assertEq(vault.lastTotalAssets(), vault.totalAssets(), "lastTotalAssets");
assertEq(vault.balanceOf(FEE_RECIPIENT), feeShares, "vault.balanceOf(FEE_RECIPIENT)");
}

function testRedeemAccrueFee(uint256 deposited, uint256 withdrawn, uint256 blocks) public {
deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS);
withdrawn = bound(withdrawn, MIN_TEST_ASSETS, deposited);
blocks = _boundBlocks(blocks);

borrowableToken.setBalance(SUPPLIER, deposited);

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

uint256 totalAssetsBefore = vault.totalAssets();

_forward(blocks);

uint256 feeShares = _feeShares(totalAssetsBefore);

uint256 shares = vault.convertToShares(withdrawn);

vm.prank(ONBEHALF);
vault.redeem(shares, RECEIVER, ONBEHALF);

assertEq(vault.lastTotalAssets(), vault.totalAssets(), "lastTotalAssets");
assertEq(vault.balanceOf(FEE_RECIPIENT), feeShares, "vault.balanceOf(FEE_RECIPIENT)");
}

function testWithdrawAccrueFee(uint256 deposited, uint256 withdrawn, uint256 blocks) public {
deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS);
withdrawn = bound(withdrawn, MIN_TEST_ASSETS, deposited);
blocks = _boundBlocks(blocks);

borrowableToken.setBalance(SUPPLIER, deposited);

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

uint256 totalAssetsBefore = vault.totalAssets();

_forward(blocks);

uint256 feeShares = _feeShares(totalAssetsBefore);

vm.prank(ONBEHALF);
vault.withdraw(withdrawn, RECEIVER, ONBEHALF);

assertEq(vault.lastTotalAssets(), vault.totalAssets(), "lastTotalAssets");
assertEq(vault.balanceOf(FEE_RECIPIENT), feeShares, "vault.balanceOf(FEE_RECIPIENT)");
}

function testSetFeeAccrueFee(uint256 deposited, uint256 fee, uint256 blocks) public {
deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS);
fee = bound(fee, 0, WAD);
blocks = _boundBlocks(blocks);

vm.assume(fee != FEE);

borrowableToken.setBalance(SUPPLIER, deposited);

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

uint256 totalAssetsBefore = vault.totalAssets();

_forward(blocks);

uint256 feeShares = _feeShares(totalAssetsBefore);

_setFee(fee);

assertEq(vault.lastTotalAssets(), vault.totalAssets(), "lastTotalAssets");
assertEq(vault.balanceOf(FEE_RECIPIENT), feeShares, "vault.balanceOf(FEE_RECIPIENT)");
}

function testSetFeeRecipientAccrueFee(uint256 deposited, uint256 blocks) public {
deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS);
blocks = _boundBlocks(blocks);

borrowableToken.setBalance(SUPPLIER, deposited);

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

uint256 totalAssetsBefore = vault.totalAssets();

_forward(blocks);

uint256 feeShares = _feeShares(totalAssetsBefore);

vm.prank(OWNER);
vault.setFeeRecipient(address(1));

assertEq(vault.lastTotalAssets(), vault.totalAssets(), "lastTotalAssets");
assertEq(vault.balanceOf(FEE_RECIPIENT), feeShares, "vault.balanceOf(FEE_RECIPIENT)");
assertEq(vault.balanceOf(address(1)), 0, "vault.balanceOf(address(1))");
}
}
Loading
Loading