@@ -24,6 +24,8 @@ contract MasterVault is ERC4626, Ownable {
2424 error NoExistingSubVault ();
2525 error MustHaveSupplyBeforeSwitchingSubVault ();
2626 error NewSubVaultExchangeRateTooLow ();
27+ error BeneficiaryNotSet ();
28+ error PerformanceFeeDisabled ();
2729
2830 // todo: avoid inflation, rounding, other common 4626 vulns
2931 // 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 {
3941 // maybe a simpler and more robust implementation would be for the owner to adjust the subVaultExchRateWad directly
4042 // this would also avoid the need for totalPrincipal tracking
4143 // 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;
4346 uint256 totalPrincipal; // total assets deposited, used to calculate profit
4447
4548 event SubvaultChanged (address indexed oldSubvault , address indexed newSubvault );
49+ event PerformanceFeeToggled (bool enabled );
50+ event BeneficiaryUpdated (address indexed oldBeneficiary , address indexed newBeneficiary );
4651
4752 constructor (IERC20 _asset , string memory _name , string memory _symbol ) ERC20 (_name, _symbol) ERC4626 (_asset) Ownable () {}
4853
@@ -137,6 +142,37 @@ contract MasterVault is ERC4626, Ownable {
137142 return subShares.mulDiv (1e18 , subVaultExchRateWad, rounding);
138143 }
139144
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+
140176 /** @dev See {IERC4626-totalAssets}. */
141177 function totalAssets () public view virtual override returns (uint256 ) {
142178 ERC4626 _subVault = subVault;
@@ -222,34 +258,13 @@ contract MasterVault is ERC4626, Ownable {
222258 uint256 assets ,
223259 uint256 shares
224260 ) internal virtual override {
261+ super ._withdraw (caller, receiver, _owner, assets, shares);
225262 ERC4626 _subVault = subVault;
226263 if (address (_subVault) != address (0 )) {
227264 _subVault.withdraw (assets, address (this ), address (this ));
228265 }
229266
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;
249268
250- ////// END PERF FEE STUFF //////
251-
252- // call super._withdraw with remaining assets
253- super ._withdraw (caller, receiver, _owner, assets, shares);
254269 }
255270}
0 commit comments