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

Test conversion rate with fee #196

Merged
merged 11 commits into from
Oct 18, 2023
2 changes: 1 addition & 1 deletion src/MetaMorpho.sol
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
}

/// @inheritdoc ERC4626
/// @dev The accrual of fees is taken into account in the conversion.
/// @dev The accrual of performance fees is taken into account in the conversion.
function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view override returns (uint256) {
(uint256 feeShares, uint256 newTotalAssets) = _accruedFeeShares();

Expand Down
105 changes: 77 additions & 28 deletions test/forge/FeeTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,30 @@ contract FeeTest is BaseTest {

// Create some debt on the market to accrue interest.

loanToken.setBalance(SUPPLIER, 1);
loanToken.setBalance(SUPPLIER, MAX_TEST_ASSETS);

vm.prank(SUPPLIER);
morpho.supply(marketParams, 1, 0, ONBEHALF, hex"");
morpho.supply(marketParams, MAX_TEST_ASSETS, 0, ONBEHALF, hex"");

uint256 collateral = uint256(1).wDivUp(marketParams.lltv);
uint256 collateral = uint256(MAX_TEST_ASSETS).wDivUp(marketParams.lltv);
collateralToken.setBalance(BORROWER, collateral);

vm.startPrank(BORROWER);
morpho.supplyCollateral(marketParams, collateral, BORROWER, hex"");
morpho.borrow(marketParams, 1, 0, BORROWER, BORROWER);
morpho.borrow(marketParams, MAX_TEST_ASSETS, 0, BORROWER, BORROWER);
vm.stopPrank();
}

_setCap(allMarkets[0], CAP);
}

function _feeShares(uint256 totalAssetsBefore) internal view returns (uint256) {
function _feeShares(uint256 lastTotalAssetsBefore) internal view returns (uint256) {
uint256 totalAssetsAfter = vault.totalAssets();
uint256 interest = totalAssetsAfter - totalAssetsBefore;
uint256 feeAmount = interest.wMulDown(FEE);
uint256 interest = totalAssetsAfter - lastTotalAssetsBefore;
Rubilmax marked this conversation as resolved.
Show resolved Hide resolved
uint256 feeAssets = interest.mulDiv(FEE, WAD);

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

Expand All @@ -58,12 +58,13 @@ contract FeeTest is BaseTest {
vm.prank(SUPPLIER);
vault.deposit(deposited, ONBEHALF);

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

function testAccrueFeeWithinABlock(uint256 deposited, uint256 withdrawn) public {
deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS);
withdrawn = bound(withdrawn, MIN_TEST_ASSETS, deposited);
deposited = bound(deposited, MIN_TEST_ASSETS + 1, MAX_TEST_ASSETS);
// The deposited amount is rounded down on Morpho and thus cannot be withdrawn in a block in most cases.
withdrawn = bound(withdrawn, MIN_TEST_ASSETS, deposited - 1);

loanToken.setBalance(SUPPLIER, deposited);

Expand All @@ -73,7 +74,7 @@ contract FeeTest is BaseTest {
vm.prank(ONBEHALF);
vault.withdraw(withdrawn, RECEIVER, ONBEHALF);

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

function testDepositAccrueFee(uint256 deposited, uint256 newDeposit, uint256 blocks) public {
Expand All @@ -86,11 +87,11 @@ contract FeeTest is BaseTest {
vm.prank(SUPPLIER);
vault.deposit(deposited, ONBEHALF);

uint256 totalAssetsBefore = vault.totalAssets();
uint256 lastTotalAssetsBefore = vault.lastTotalAssets();

_forward(blocks);

uint256 feeShares = _feeShares(totalAssetsBefore);
uint256 feeShares = _feeShares(lastTotalAssetsBefore);

loanToken.setBalance(SUPPLIER, newDeposit);

Expand All @@ -111,11 +112,11 @@ contract FeeTest is BaseTest {
vm.prank(SUPPLIER);
vault.deposit(deposited, ONBEHALF);

uint256 totalAssetsBefore = vault.totalAssets();
uint256 lastTotalAssetsBefore = vault.lastTotalAssets();

_forward(blocks);

uint256 feeShares = _feeShares(totalAssetsBefore);
uint256 feeShares = _feeShares(lastTotalAssetsBefore);

uint256 shares = vault.convertToShares(newDeposit);

Expand All @@ -138,11 +139,11 @@ contract FeeTest is BaseTest {
vm.prank(SUPPLIER);
vault.deposit(deposited, ONBEHALF);

uint256 totalAssetsBefore = vault.totalAssets();
uint256 lastTotalAssetsBefore = vault.lastTotalAssets();

_forward(blocks);

uint256 feeShares = _feeShares(totalAssetsBefore);
uint256 feeShares = _feeShares(lastTotalAssetsBefore);

uint256 shares = vault.convertToShares(withdrawn);

Expand All @@ -163,11 +164,11 @@ contract FeeTest is BaseTest {
vm.prank(SUPPLIER);
vault.deposit(deposited, ONBEHALF);

uint256 totalAssetsBefore = vault.totalAssets();
uint256 lastTotalAssetsBefore = vault.lastTotalAssets();

_forward(blocks);

uint256 feeShares = _feeShares(totalAssetsBefore);
uint256 feeShares = _feeShares(lastTotalAssetsBefore);

vm.prank(ONBEHALF);
vault.withdraw(withdrawn, RECEIVER, ONBEHALF);
Expand All @@ -178,21 +179,19 @@ contract FeeTest is BaseTest {

function testSetFeeAccrueFee(uint256 deposited, uint256 fee, uint256 blocks) public {
deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS);
fee = bound(fee, 0, MAX_FEE);
fee = bound(fee, 0, FEE - 1);
blocks = _boundBlocks(blocks);

vm.assume(fee != FEE);

loanToken.setBalance(SUPPLIER, deposited);

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

uint256 totalAssetsBefore = vault.totalAssets();
uint256 lastTotalAssetsBefore = vault.lastTotalAssets();

_forward(blocks);

uint256 feeShares = _feeShares(totalAssetsBefore);
uint256 feeShares = _feeShares(lastTotalAssetsBefore);

_setFee(fee);

Expand All @@ -209,11 +208,11 @@ contract FeeTest is BaseTest {
vm.prank(SUPPLIER);
vault.deposit(deposited, ONBEHALF);

uint256 totalAssetsBefore = vault.totalAssets();
uint256 lastTotalAssetsBefore = vault.lastTotalAssets();

_forward(blocks);

uint256 feeShares = _feeShares(totalAssetsBefore);
uint256 feeShares = _feeShares(lastTotalAssetsBefore);

vm.prank(OWNER);
vault.setFeeRecipient(address(1));
Expand Down Expand Up @@ -253,4 +252,54 @@ contract FeeTest is BaseTest {
vm.expectRevert(ErrorsLib.ZeroFeeRecipient.selector);
vault.setFeeRecipient(address(0));
}

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

loanToken.setBalance(SUPPLIER, deposited);

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

uint256 lastTotalAssetsBefore = vault.lastTotalAssets();
uint256 sharesBefore = vault.convertToShares(assets);

_forward(blocks);

uint256 feeShares = _feeShares(lastTotalAssetsBefore);
uint256 expectedShares = assets.mulDiv(
vault.totalSupply() + feeShares + 10 ** DECIMALS_OFFSET, vault.totalAssets() + 1, Math.Rounding.Floor
);
uint256 shares = vault.convertToShares(assets);

assertEq(shares, expectedShares, "shares");
assertLt(shares, sharesBefore, "shares decreased");
}

function testConvertToSharesWithFeeAndInterest(uint256 deposited, uint256 shares, uint256 blocks) public {
deposited = bound(deposited, MIN_TEST_ASSETS, MAX_TEST_ASSETS);
shares = bound(shares, 10 ** DECIMALS_OFFSET, MAX_TEST_ASSETS);
blocks = _boundBlocks(blocks);

loanToken.setBalance(SUPPLIER, deposited);

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

uint256 lastTotalAssetsBefore = vault.lastTotalAssets();
uint256 assetsBefore = vault.convertToAssets(shares);

_forward(blocks);

uint256 feeShares = _feeShares(lastTotalAssetsBefore);
uint256 expectedAssets = shares.mulDiv(
vault.totalAssets() + 1, vault.totalSupply() + feeShares + 10 ** DECIMALS_OFFSET, Math.Rounding.Floor
);
uint256 assets = vault.convertToAssets(shares);

assertEq(assets, expectedAssets, "assets");
assertGe(assets, assetsBefore, "assets increased");
}
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved
}
2 changes: 1 addition & 1 deletion test/forge/helpers/BaseTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ contract BaseTest is Test {

/// @dev Bounds the fuzzing input to a realistic number of blocks.
function _boundBlocks(uint256 blocks) internal view returns (uint256) {
return bound(blocks, 1, type(uint24).max);
return bound(blocks, 2, type(uint24).max);
Rubilmax marked this conversation as resolved.
Show resolved Hide resolved
}

/// @dev Bounds the fuzzing input to a non-zero address.
Expand Down
Loading