Skip to content

Commit 8d3bfde

Browse files
Merge pull request #349 from euler-xyz/fee-flow-util
FeeFlowControllerUtil
2 parents 847209d + 9e49d83 commit 8d3bfde

File tree

2 files changed

+222
-0
lines changed

2 files changed

+222
-0
lines changed

src/Util/FeeFlowControllerUtil.sol

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
3+
pragma solidity ^0.8.0;
4+
5+
import {ReentrancyGuard} from "openzeppelin-contracts/utils/ReentrancyGuard.sol";
6+
import {IERC20, SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
7+
import {FeeFlowController} from "fee-flow/FeeFlowController.sol";
8+
import {EVCUtil} from "evc/utils/EVCUtil.sol";
9+
import {IEVault} from "evk/EVault/IEVault.sol";
10+
11+
/// @title FeeFlowControllerUtil
12+
/// @custom:security-contact security@euler.xyz
13+
/// @author Euler Labs (https://www.eulerlabs.com/)
14+
/// @notice A contract that allows users to convert fees from multiple vaults and participate in the fee flow dutch
15+
/// auction.
16+
contract FeeFlowControllerUtil is EVCUtil, ReentrancyGuard {
17+
address public immutable feeFlowController;
18+
IERC20 public immutable paymentToken;
19+
20+
constructor(address _feeFlowController) EVCUtil(FeeFlowController(_feeFlowController).EVC()) {
21+
feeFlowController = _feeFlowController;
22+
paymentToken = IERC20(address(FeeFlowController(feeFlowController).paymentToken()));
23+
SafeERC20.forceApprove(paymentToken, feeFlowController, type(uint256).max);
24+
}
25+
26+
function buy(
27+
address[] calldata assets,
28+
address assetsReceiver,
29+
uint256 epochId,
30+
uint256 deadline,
31+
uint256 maxPaymentTokenAmount
32+
) external nonReentrant returns (uint256) {
33+
uint256 paymentAmount = FeeFlowController(feeFlowController).getPrice();
34+
35+
if (paymentAmount > maxPaymentTokenAmount) {
36+
revert FeeFlowController.MaxPaymentTokenAmountExceeded();
37+
}
38+
39+
if (paymentAmount > 0) {
40+
SafeERC20.safeTransferFrom(paymentToken, _msgSender(), address(this), paymentAmount);
41+
}
42+
43+
for (uint256 i = 0; i < assets.length; ++i) {
44+
IEVault(assets[i]).convertFees();
45+
}
46+
47+
return
48+
FeeFlowController(feeFlowController).buy(assets, assetsReceiver, epochId, deadline, maxPaymentTokenAmount);
49+
}
50+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
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

Comments
 (0)