@@ -24,6 +24,8 @@ contract MasterVault is ERC4626, Ownable {
24
24
error NoExistingSubVault ();
25
25
error MustHaveSupplyBeforeSwitchingSubVault ();
26
26
error NewSubVaultExchangeRateTooLow ();
27
+ error BeneficiaryNotSet ();
28
+ error PerformanceFeeDisabled ();
27
29
28
30
// todo: avoid inflation, rounding, other common 4626 vulns
29
31
// we may need a minimum asset or master share amount when setting subvaults (bc of exchange rate calc)
@@ -39,10 +41,13 @@ contract MasterVault is ERC4626, Ownable {
39
41
// maybe a simpler and more robust implementation would be for the owner to adjust the subVaultExchRateWad directly
40
42
// this would also avoid the need for totalPrincipal tracking
41
43
// however, this would require more trust in the owner
42
- uint256 public performanceFeeBps; // in basis points, e.g. 200 = 2% | todo a way to set this
44
+ bool public enablePerformanceFee;
45
+ address public beneficiary;
43
46
uint256 totalPrincipal; // total assets deposited, used to calculate profit
44
47
45
48
event SubvaultChanged (address indexed oldSubvault , address indexed newSubvault );
49
+ event PerformanceFeeToggled (bool enabled );
50
+ event BeneficiaryUpdated (address indexed oldBeneficiary , address indexed newBeneficiary );
46
51
47
52
constructor (IERC20 _asset , string memory _name , string memory _symbol ) ERC20 (_name, _symbol) ERC4626 (_asset) Ownable () {}
48
53
@@ -137,6 +142,37 @@ contract MasterVault is ERC4626, Ownable {
137
142
return subShares.mulDiv (1e18 , subVaultExchRateWad, rounding);
138
143
}
139
144
145
+ /// @notice Toggle performance fee collection on/off
146
+ /// @param enabled True to enable performance fees, false to disable
147
+ function setPerformanceFee (bool enabled ) external onlyOwner {
148
+ enablePerformanceFee = enabled;
149
+ emit PerformanceFeeToggled (enabled);
150
+ }
151
+
152
+ /// @notice Set the beneficiary address for performance fees
153
+ /// @param newBeneficiary Address to receive performance fees, zero address defaults to owner
154
+ function setBeneficiary (address newBeneficiary ) external onlyOwner {
155
+ address oldBeneficiary = beneficiary;
156
+ beneficiary = newBeneficiary;
157
+ emit BeneficiaryUpdated (oldBeneficiary, newBeneficiary);
158
+ }
159
+
160
+ /// @notice Withdraw all accumulated performance fees to beneficiary
161
+ /// @dev Only callable by owner when performance fees are enabled
162
+ function withdrawPerformanceFees () external onlyOwner {
163
+ if (! enablePerformanceFee) revert PerformanceFeeDisabled ();
164
+ if (beneficiary == address (0 )) revert BeneficiaryNotSet ();
165
+
166
+ uint256 totalProfits = totalProfit ();
167
+ if (totalProfits > 0 ) {
168
+ ERC4626 _subVault = subVault;
169
+ if (address (_subVault) != address (0 )) {
170
+ _subVault.withdraw (totalProfits, address (this ), address (this ));
171
+ }
172
+ IERC20 (asset ()).safeTransfer (beneficiary, totalProfits);
173
+ }
174
+ }
175
+
140
176
/** @dev See {IERC4626-totalAssets}. */
141
177
function totalAssets () public view virtual override returns (uint256 ) {
142
178
ERC4626 _subVault = subVault;
@@ -222,34 +258,13 @@ contract MasterVault is ERC4626, Ownable {
222
258
uint256 assets ,
223
259
uint256 shares
224
260
) internal virtual override {
261
+ super ._withdraw (caller, receiver, _owner, assets, shares);
225
262
ERC4626 _subVault = subVault;
226
263
if (address (_subVault) != address (0 )) {
227
264
_subVault.withdraw (assets, address (this ), address (this ));
228
265
}
229
266
230
- ////// PERF FEE STUFF //////
231
- // determine profit portion and principal portion of assets
232
- uint256 _totalProfit = totalProfit ();
233
- // use shares because they are rounded up vs assets which are rounded down
234
- uint256 profitPortion = shares.mulDiv (_totalProfit, totalSupply (), Math.Rounding.Up);
235
- uint256 principalPortion = assets - profitPortion;
236
-
237
- // subtract principal portion from totalPrincipal
238
- totalPrincipal -= principalPortion;
239
-
240
- // send fee to owner (todo should be a separate beneficiary addr set by owner)
241
- if (performanceFeeBps > 0 && profitPortion > 0 ) {
242
- uint256 fee = profitPortion.mulDiv (performanceFeeBps, 10000 , Math.Rounding.Up);
243
- // send fee to owner
244
- IERC20 (asset ()).safeTransfer (owner (), fee);
245
-
246
- // note subtraction
247
- assets -= fee;
248
- }
267
+ totalPrincipal -= assets;
249
268
250
- ////// END PERF FEE STUFF //////
251
-
252
- // call super._withdraw with remaining assets
253
- super ._withdraw (caller, receiver, _owner, assets, shares);
254
269
}
255
270
}
0 commit comments