11// SPDX-License-Identifier: Apache-2.0
22pragma solidity ^ 0.8.0 ;
33
4- import {ERC4626 } from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol " ;
5- import { IERC20 , ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol " ;
6- import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol " ;
4+ import {ERC4626Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol " ;
5+ import {ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol " ;
6+ import {IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol " ;
7+ import {IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol " ;
8+ import {IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol " ;
9+ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol " ;
10+ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol " ;
711import {IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol " ;
8- import {Math } from "@openzeppelin/contracts/utils/math/Math .sol " ;
12+ import {MathUpgradeable } from "@openzeppelin/contracts-upgradeable /utils/math/MathUpgradeable .sol " ;
913import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol " ;
1014
11- contract MasterVault is ERC4626 , Ownable {
15+ contract MasterVault is Initializable , ERC4626Upgradeable , OwnableUpgradeable {
1216 using SafeERC20 for IERC20 ;
13- using Math for uint256 ;
17+ using MathUpgradeable for uint256 ;
1418
1519 error TooFewSharesReceived ();
1620 error TooManySharesBurned ();
@@ -26,16 +30,18 @@ contract MasterVault is ERC4626, Ownable {
2630 error NewSubVaultExchangeRateTooLow ();
2731 error BeneficiaryNotSet ();
2832 error PerformanceFeeDisabled ();
33+ error InvalidAsset ();
34+ error InvalidOwner ();
2935
3036 // todo: avoid inflation, rounding, other common 4626 vulns
3137 // we may need a minimum asset or master share amount when setting subvaults (bc of exchange rate calc)
32- ERC4626 public subVault;
38+ IERC4626 public subVault;
3339
3440 // how many subVault shares one MV2 share can be redeemed for
3541 // initially 1 to 1
3642 // constant per subvault
3743 // changes when subvault is set
38- uint256 public subVaultExchRateWad = 1e18 ;
44+ uint256 public subVaultExchRateWad;
3945
4046 // note: the performance fee can be avoided if the underlying strategy can be sandwiched (eg ETH to wstETH dex swap)
4147 // maybe a simpler and more robust implementation would be for the owner to adjust the subVaultExchRateWad directly
@@ -49,16 +55,26 @@ contract MasterVault is ERC4626, Ownable {
4955 event PerformanceFeeToggled (bool enabled );
5056 event BeneficiaryUpdated (address indexed oldBeneficiary , address indexed newBeneficiary );
5157
52- constructor (IERC20 _asset , string memory _name , string memory _symbol ) ERC20 (_name, _symbol) ERC4626 (_asset) Ownable () {}
58+ function initialize (IERC20 _asset , string memory _name , string memory _symbol , address _owner ) external initializer {
59+ if (address (_asset) == address (0 )) revert InvalidAsset ();
60+ if (_owner == address (0 )) revert InvalidOwner ();
61+
62+ __ERC20_init (_name, _symbol);
63+ __ERC4626_init (IERC20Upgradeable (address (_asset)));
64+ _transferOwnership (_owner);
65+
66+ subVaultExchRateWad = 1e18 ;
67+ }
68+
5369
5470 function deposit (uint256 assets , address receiver , uint256 minSharesMinted ) public returns (uint256 ) {
55- uint256 shares = super . deposit (assets, receiver);
71+ uint256 shares = deposit (assets, receiver);
5672 if (shares < minSharesMinted) revert TooFewSharesReceived ();
5773 return shares;
5874 }
5975
6076 function withdraw (uint256 assets , address receiver , address _owner , uint256 maxSharesBurned ) public returns (uint256 ) {
61- uint256 shares = super . withdraw (assets, receiver, _owner);
77+ uint256 shares = withdraw (assets, receiver, _owner);
6278 if (shares > maxSharesBurned) revert TooManySharesBurned ();
6379 return shares;
6480 }
@@ -78,7 +94,7 @@ contract MasterVault is ERC4626, Ownable {
7894 /// @notice Set a subvault. Can only be called if there is not already a subvault set.
7995 /// @param _subVault The subvault to set. Must be an ERC4626 vault with the same asset as this MasterVault.
8096 /// @param minSubVaultExchRateWad Minimum acceptable ratio (times 1e18) of new subvault shares to outstanding MasterVault shares after deposit.
81- function setSubVault (ERC4626 _subVault , uint256 minSubVaultExchRateWad ) external onlyOwner {
97+ function setSubVault (IERC4626 _subVault , uint256 minSubVaultExchRateWad ) external onlyOwner {
8298 if (address (subVault) != address (0 )) revert SubVaultAlreadySet ();
8399 _setSubVault (_subVault, minSubVaultExchRateWad);
84100 }
@@ -89,34 +105,34 @@ contract MasterVault is ERC4626, Ownable {
89105 _revokeSubVault (minAssetExchRateWad);
90106 }
91107
92- function _setSubVault (ERC4626 _subVault , uint256 minSubVaultExchRateWad ) internal {
108+ function _setSubVault (IERC4626 _subVault , uint256 minSubVaultExchRateWad ) internal {
93109 if (address (_subVault) == address (0 )) revert SubVaultCannotBeZeroAddress ();
94110 if (totalSupply () == 0 ) revert MustHaveSupplyBeforeSettingSubVault ();
95111 if (address (_subVault.asset ()) != address (asset ())) revert SubVaultAssetMismatch ();
96112
97113 IERC20 (asset ()).safeApprove (address (_subVault), type (uint256 ).max);
98114 uint256 subShares = _subVault.deposit (totalAssets (), address (this ));
99115
100- uint256 _subVaultExchRateWad = subShares.mulDiv (1e18 , totalSupply (), Math.Rounding.Down);
116+ subVault = _subVault;
117+
118+ uint256 _subVaultExchRateWad = subShares.mulDiv (1e18 , totalAssets (), MathUpgradeable.Rounding.Down);
101119 if (_subVaultExchRateWad < minSubVaultExchRateWad) revert SubVaultExchangeRateTooLow ();
102120 subVaultExchRateWad = _subVaultExchRateWad;
103121
104- subVault = _subVault;
105-
106122 emit SubvaultChanged (address (0 ), address (_subVault));
107123 }
108124
109125 function _revokeSubVault (uint256 minAssetExchRateWad ) internal {
110- ERC4626 oldSubVault = subVault;
126+ IERC4626 oldSubVault = subVault;
111127 if (address (oldSubVault) == address (0 )) revert NoExistingSubVault ();
112128
113129 uint256 _totalSupply = totalSupply ();
114130 uint256 assetReceived = oldSubVault.withdraw (oldSubVault.maxWithdraw (address (this )), address (this ), address (this ));
115- uint256 effectiveAssetExchRateWad = assetReceived.mulDiv (1e18 , _totalSupply, Math .Rounding.Down);
131+ uint256 effectiveAssetExchRateWad = assetReceived.mulDiv (1e18 , _totalSupply, MathUpgradeable .Rounding.Down);
116132 if (effectiveAssetExchRateWad < minAssetExchRateWad) revert TooFewAssetsReceived ();
117133
118134 IERC20 (asset ()).safeApprove (address (oldSubVault), 0 );
119- subVault = ERC4626 (address (0 ));
135+ subVault = IERC4626 (address (0 ));
120136 subVaultExchRateWad = 1e18 ;
121137
122138 emit SubvaultChanged (address (oldSubVault), address (0 ));
@@ -126,19 +142,19 @@ contract MasterVault is ERC4626, Ownable {
126142 /// @param newSubVault The new subvault to switch to, or zero address to revoke current subvault
127143 /// @param minAssetExchRateWad Minimum acceptable ratio (times 1e18) of assets received from old subvault to outstanding MasterVault shares
128144 /// @param minNewSubVaultExchRateWad Minimum acceptable ratio (times 1e18) of new subvault shares to outstanding MasterVault shares after deposit
129- function switchSubVault (ERC4626 newSubVault , uint256 minAssetExchRateWad , uint256 minNewSubVaultExchRateWad ) external onlyOwner {
145+ function switchSubVault (IERC4626 newSubVault , uint256 minAssetExchRateWad , uint256 minNewSubVaultExchRateWad ) external onlyOwner {
130146 _revokeSubVault (minAssetExchRateWad);
131147
132148 if (address (newSubVault) != address (0 )) {
133149 _setSubVault (newSubVault, minNewSubVaultExchRateWad);
134150 }
135151 }
136152
137- function masterSharesToSubShares (uint256 masterShares , Math .Rounding rounding ) public view returns (uint256 ) {
153+ function masterSharesToSubShares (uint256 masterShares , MathUpgradeable .Rounding rounding ) public view returns (uint256 ) {
138154 return masterShares.mulDiv (subVaultExchRateWad, 1e18 , rounding);
139155 }
140156
141- function subSharesToMasterShares (uint256 subShares , Math .Rounding rounding ) public view returns (uint256 ) {
157+ function subSharesToMasterShares (uint256 subShares , MathUpgradeable .Rounding rounding ) public view returns (uint256 ) {
142158 return subShares.mulDiv (1e18 , subVaultExchRateWad, rounding);
143159 }
144160
@@ -165,7 +181,7 @@ contract MasterVault is ERC4626, Ownable {
165181
166182 uint256 totalProfits = totalProfit ();
167183 if (totalProfits > 0 ) {
168- ERC4626 _subVault = subVault;
184+ IERC4626 _subVault = subVault;
169185 if (address (_subVault) != address (0 )) {
170186 _subVault.withdraw (totalProfits, address (this ), address (this ));
171187 }
@@ -175,7 +191,7 @@ contract MasterVault is ERC4626, Ownable {
175191
176192 /** @dev See {IERC4626-totalAssets}. */
177193 function totalAssets () public view virtual override returns (uint256 ) {
178- ERC4626 _subVault = subVault;
194+ IERC4626 _subVault = subVault;
179195 if (address (_subVault) == address (0 )) {
180196 return super .totalAssets ();
181197 }
@@ -196,7 +212,7 @@ contract MasterVault is ERC4626, Ownable {
196212 if (subShares == type (uint256 ).max) {
197213 return type (uint256 ).max;
198214 }
199- return subSharesToMasterShares (subShares, Math .Rounding.Down);
215+ return subSharesToMasterShares (subShares, MathUpgradeable .Rounding.Down);
200216 }
201217
202218 /**
@@ -205,25 +221,25 @@ contract MasterVault is ERC4626, Ownable {
205221 * Will revert if assets > 0, totalSupply > 0 and totalAssets = 0. That corresponds to a case where any asset
206222 * would represent an infinite amount of shares.
207223 */
208- function _convertToShares (uint256 assets , Math .Rounding rounding ) internal view virtual override returns (uint256 shares ) {
209- ERC4626 _subVault = subVault;
224+ function _convertToShares (uint256 assets , MathUpgradeable .Rounding rounding ) internal view virtual override returns (uint256 shares ) {
225+ IERC4626 _subVault = subVault;
210226 if (address (_subVault) == address (0 )) {
211227 return super ._convertToShares (assets, rounding);
212228 }
213- uint256 subShares = rounding == Math .Rounding.Up ? _subVault.previewWithdraw (assets) : _subVault.previewDeposit (assets);
229+ uint256 subShares = rounding == MathUpgradeable .Rounding.Up ? _subVault.previewWithdraw (assets) : _subVault.previewDeposit (assets);
214230 return subSharesToMasterShares (subShares, rounding);
215231 }
216232
217233 /**
218234 * @dev Internal conversion function (from shares to assets) with support for rounding direction.
219235 */
220- function _convertToAssets (uint256 shares , Math .Rounding rounding ) internal view virtual override returns (uint256 assets ) {
221- ERC4626 _subVault = subVault;
236+ function _convertToAssets (uint256 shares , MathUpgradeable .Rounding rounding ) internal view virtual override returns (uint256 assets ) {
237+ IERC4626 _subVault = subVault;
222238 if (address (_subVault) == address (0 )) {
223239 return super ._convertToAssets (shares, rounding);
224240 }
225241 uint256 subShares = masterSharesToSubShares (shares, rounding);
226- return rounding == Math .Rounding.Up ? _subVault.previewMint (subShares) : _subVault.previewRedeem (subShares);
242+ return rounding == MathUpgradeable .Rounding.Up ? _subVault.previewMint (subShares) : _subVault.previewRedeem (subShares);
227243 }
228244
229245 function totalProfit () public view returns (uint256 ) {
@@ -241,10 +257,11 @@ contract MasterVault is ERC4626, Ownable {
241257 uint256 shares
242258 ) internal virtual override {
243259 super ._deposit (caller, receiver, assets, shares);
260+
244261 totalPrincipal += assets;
245- ERC4626 _subVault = subVault;
262+ IERC4626 _subVault = subVault;
246263 if (address (_subVault) != address (0 )) {
247- _subVault.deposit (assets, address (this ));
264+ _subVault.deposit (assets, address (this ));
248265 }
249266 }
250267
@@ -260,7 +277,7 @@ contract MasterVault is ERC4626, Ownable {
260277 ) internal virtual override {
261278 totalPrincipal -= assets;
262279
263- ERC4626 _subVault = subVault;
280+ IERC4626 _subVault = subVault;
264281 if (address (_subVault) != address (0 )) {
265282 _subVault.withdraw (assets, address (this ), address (this ));
266283 }
0 commit comments