|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
| 2 | + |
| 3 | +pragma solidity ^0.8.0; |
| 4 | + |
| 5 | +import {EVaultTestBase} from "../../lib/euler-vault-kit/test/unit/evault/EVaultTestBase.t.sol"; |
| 6 | +import {IEVault} from "../../lib/euler-vault-kit/src/EVault/IEVault.sol"; |
| 7 | +import {FeeFlowController} from "fee-flow/FeeFlowController.sol"; |
| 8 | +import {FeeFlowControllerUtil} from "../../src/Util/FeeFlowControllerUtil.sol"; |
| 9 | +import {TestERC20} from "evk-test/mocks/TestERC20.sol"; |
| 10 | +import {IRMTestDefault} from "evk-test/mocks/IRMTestDefault.sol"; |
| 11 | + |
| 12 | +contract FeeFlowControllerUtilTest is EVaultTestBase { |
| 13 | + // Constants for FeeFlowController |
| 14 | + uint256 public constant INIT_PRICE = 1e18; |
| 15 | + uint256 public constant MIN_INIT_PRICE = 1e6; |
| 16 | + uint256 public constant EPOCH_PERIOD = 14 days; |
| 17 | + uint256 public constant PRICE_MULTIPLIER = 2e18; |
| 18 | + |
| 19 | + // Test contracts |
| 20 | + FeeFlowController public feeFlowController; |
| 21 | + FeeFlowControllerUtil public feeFlowControllerUtil; |
| 22 | + TestERC20 public paymentToken; |
| 23 | + |
| 24 | + // Test users |
| 25 | + address public buyer = makeAddr("buyer"); |
| 26 | + address public assetsReceiver = makeAddr("assetsReceiver"); |
| 27 | + address public paymentReceiver = makeAddr("paymentReceiver"); |
| 28 | + |
| 29 | + function setUp() public override { |
| 30 | + super.setUp(); |
| 31 | + |
| 32 | + // Deploy payment token |
| 33 | + paymentToken = new TestERC20("Payment Token", "PAY", 18, false); |
| 34 | + |
| 35 | + // Deploy FeeFlowController |
| 36 | + feeFlowController = new FeeFlowController( |
| 37 | + address(evc), |
| 38 | + INIT_PRICE, |
| 39 | + address(paymentToken), |
| 40 | + paymentReceiver, |
| 41 | + EPOCH_PERIOD, |
| 42 | + PRICE_MULTIPLIER, |
| 43 | + MIN_INIT_PRICE |
| 44 | + ); |
| 45 | + |
| 46 | + // Deploy FeeFlowControllerUtil |
| 47 | + feeFlowControllerUtil = new FeeFlowControllerUtil(address(feeFlowController)); |
| 48 | + |
| 49 | + // Set up oracle prices |
| 50 | + oracle.setPrice(address(assetTST), unitOfAccount, 1e18); |
| 51 | + oracle.setPrice(address(assetTST2), unitOfAccount, 1e18); |
| 52 | + |
| 53 | + // Set up mutual LTVs for collateralization |
| 54 | + eTST.setLTV(address(eTST2), 0.8e4, 0.8e4, 0); |
| 55 | + eTST2.setLTV(address(eTST), 0.8e4, 0.8e4, 0); |
| 56 | + |
| 57 | + // Set interest fees to generate fees |
| 58 | + eTST.setInterestFee(0.1e4); // 10% interest fee |
| 59 | + eTST2.setInterestFee(0.1e4); |
| 60 | + |
| 61 | + // Set FeeFlowController as the fee receiver so converted fees go there |
| 62 | + eTST.setFeeReceiver(address(feeFlowController)); |
| 63 | + eTST2.setFeeReceiver(address(feeFlowController)); |
| 64 | + |
| 65 | + // Mint payment tokens to buyer |
| 66 | + paymentToken.mint(buyer, 1000000e18); |
| 67 | + |
| 68 | + // Approve payment token from buyer to FeeFlowControllerUtil |
| 69 | + vm.startPrank(buyer); |
| 70 | + paymentToken.approve(address(feeFlowControllerUtil), type(uint256).max); |
| 71 | + vm.stopPrank(); |
| 72 | + } |
| 73 | + |
| 74 | + function testConstructor() public view { |
| 75 | + assertEq(address(feeFlowControllerUtil.feeFlowController()), address(feeFlowController)); |
| 76 | + assertEq(address(feeFlowControllerUtil.paymentToken()), address(paymentToken)); |
| 77 | + assertEq(paymentToken.allowance(address(feeFlowControllerUtil), address(feeFlowController)), type(uint256).max); |
| 78 | + } |
| 79 | + |
| 80 | + function testBuy_MultipleVaults() public { |
| 81 | + // Set up depositors and borrowers to generate fees in multiple vaults |
| 82 | + address depositor1 = makeAddr("depositor1"); |
| 83 | + address depositor2 = makeAddr("depositor2"); |
| 84 | + address borrower1 = makeAddr("borrower1"); |
| 85 | + address borrower2 = makeAddr("borrower2"); |
| 86 | + |
| 87 | + // Fund depositor1 for eTST |
| 88 | + assetTST.mint(depositor1, 1000e18); |
| 89 | + vm.startPrank(depositor1); |
| 90 | + assetTST.approve(address(eTST), type(uint256).max); |
| 91 | + eTST.deposit(1000e18, depositor1); |
| 92 | + vm.stopPrank(); |
| 93 | + |
| 94 | + // Fund depositor2 for eTST2 |
| 95 | + assetTST2.mint(depositor2, 1000e18); |
| 96 | + vm.startPrank(depositor2); |
| 97 | + assetTST2.approve(address(eTST2), type(uint256).max); |
| 98 | + eTST2.deposit(1000e18, depositor2); |
| 99 | + vm.stopPrank(); |
| 100 | + |
| 101 | + // Fund borrower1 with collateral and borrow from eTST |
| 102 | + assetTST2.mint(borrower1, 1000e18); |
| 103 | + vm.startPrank(borrower1); |
| 104 | + assetTST2.approve(address(eTST2), type(uint256).max); |
| 105 | + eTST2.deposit(800e18, borrower1); |
| 106 | + evc.enableCollateral(borrower1, address(eTST2)); |
| 107 | + evc.enableController(borrower1, address(eTST)); |
| 108 | + eTST.borrow(400e18, borrower1); // Reduced borrow amount |
| 109 | + vm.stopPrank(); |
| 110 | + |
| 111 | + // Fund borrower2 with collateral and borrow from eTST2 |
| 112 | + assetTST.mint(borrower2, 1000e18); |
| 113 | + vm.startPrank(borrower2); |
| 114 | + assetTST.approve(address(eTST), type(uint256).max); |
| 115 | + eTST.deposit(800e18, borrower2); |
| 116 | + evc.enableCollateral(borrower2, address(eTST)); |
| 117 | + evc.enableController(borrower2, address(eTST2)); |
| 118 | + eTST2.borrow(400e18, borrower2); // Reduced borrow amount |
| 119 | + vm.stopPrank(); |
| 120 | + |
| 121 | + // Let time pass to accumulate fees |
| 122 | + skip(365 days); |
| 123 | + |
| 124 | + // Check that fees have accumulated |
| 125 | + uint256 accumulatedFees1 = eTST.accumulatedFees(); |
| 126 | + uint256 accumulatedFees2 = eTST2.accumulatedFees(); |
| 127 | + assertGt(accumulatedFees1, 0); |
| 128 | + assertGt(accumulatedFees2, 0); |
| 129 | + |
| 130 | + // Get current epoch info |
| 131 | + FeeFlowController.Slot0 memory slot0 = feeFlowController.getSlot0(); |
| 132 | + uint256 currentPrice = feeFlowController.getPrice(); |
| 133 | + |
| 134 | + // Prepare assets array |
| 135 | + address[] memory assets = new address[](2); |
| 136 | + assets[0] = address(eTST); |
| 137 | + assets[1] = address(eTST2); |
| 138 | + |
| 139 | + // Get initial balances |
| 140 | + uint256 buyerPaymentBalanceBefore = paymentToken.balanceOf(buyer); |
| 141 | + uint256 assetsReceiverTSTBalanceBefore = eTST.balanceOf(assetsReceiver); |
| 142 | + uint256 assetsReceiverTST2BalanceBefore = eTST2.balanceOf(assetsReceiver); |
| 143 | + |
| 144 | + // Execute buy |
| 145 | + vm.startPrank(buyer); |
| 146 | + uint256 paymentAmount = feeFlowControllerUtil.buy( |
| 147 | + assets, assetsReceiver, slot0.epochId, block.timestamp + 1 hours, currentPrice + 1e18 |
| 148 | + ); |
| 149 | + vm.stopPrank(); |
| 150 | + |
| 151 | + // Verify payment amount |
| 152 | + assertEq(paymentAmount, currentPrice); |
| 153 | + |
| 154 | + // Verify payment token transfer |
| 155 | + assertEq(paymentToken.balanceOf(buyer), buyerPaymentBalanceBefore - paymentAmount); |
| 156 | + assertEq(paymentToken.balanceOf(paymentReceiver), paymentAmount); |
| 157 | + |
| 158 | + // Verify assets were transferred to receiver |
| 159 | + // The receiver should have received vault tokens from the FeeFlowController |
| 160 | + assertGt(eTST.balanceOf(assetsReceiver), assetsReceiverTSTBalanceBefore); |
| 161 | + assertGt(eTST2.balanceOf(assetsReceiver), assetsReceiverTST2BalanceBefore); |
| 162 | + |
| 163 | + // The FeeFlowController should have 0 balance after the buy operation |
| 164 | + // (all vault tokens should have been transferred to the buyer) |
| 165 | + assertEq(eTST.balanceOf(address(feeFlowController)), 0); |
| 166 | + assertEq(eTST2.balanceOf(address(feeFlowController)), 0); |
| 167 | + |
| 168 | + // Verify fees were converted in both vaults |
| 169 | + assertEq(eTST.accumulatedFees(), 0); |
| 170 | + assertEq(eTST2.accumulatedFees(), 0); |
| 171 | + } |
| 172 | +} |
0 commit comments