Skip to content

Commit e9d49e5

Browse files
authored
feat: make master vault upgradable
Make master vault upgradable
2 parents fc89964 + 9ff2c39 commit e9d49e5

File tree

6 files changed

+161
-58
lines changed

6 files changed

+161
-58
lines changed

contracts/tokenbridge/libraries/vault/IMasterVault.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
pragma solidity ^0.8.0;
33

44
interface IMasterVault {
5-
function setSubVault(address subVault) external;
5+
function setSubVault(address subVault, uint256 minSubVaultExchRateWad) external;
66
}

contracts/tokenbridge/libraries/vault/IMasterVaultFactory.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ interface IMasterVaultFactory {
99
function deployVault(address token) external returns (address vault);
1010
function calculateVaultAddress(address token) external view returns (address);
1111
function getVault(address token) external returns (address);
12-
function setSubVault(address masterVault, address subVault) external;
12+
function setSubVault(address masterVault, address subVault, uint256 minSubVaultExchRateWad) external;
1313
}

contracts/tokenbridge/libraries/vault/MasterVault.sol

Lines changed: 51 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
// SPDX-License-Identifier: Apache-2.0
22
pragma 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";
711
import {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";
913
import {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
}

contracts/tokenbridge/libraries/vault/MasterVaultFactory.sol

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,49 +5,57 @@ pragma solidity ^0.8.0;
55
import "@openzeppelin/contracts/utils/Create2.sol";
66
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
77
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
8+
import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
9+
import "../ClonableBeaconProxy.sol";
810
import "./IMasterVault.sol";
911
import "./IMasterVaultFactory.sol";
1012
import "./MasterVault.sol";
1113

1214
contract MasterVaultFactory is IMasterVaultFactory, OwnableUpgradeable {
13-
1415
error ZeroAddress();
16+
error BeaconNotDeployed();
17+
18+
BeaconProxyFactory public beaconProxyFactory;
1519

1620
function initialize(address _owner) public initializer {
1721
_transferOwnership(_owner);
22+
23+
MasterVault masterVaultImplementation = new MasterVault();
24+
UpgradeableBeacon beacon = new UpgradeableBeacon(address(masterVaultImplementation));
25+
beaconProxyFactory = new BeaconProxyFactory();
26+
beaconProxyFactory.initialize(address(beacon));
27+
beacon.transferOwnership(_owner);
1828
}
1929

2030
function deployVault(address token) public returns (address vault) {
2131
if (token == address(0)) {
2232
revert ZeroAddress();
2333
}
34+
if (
35+
address(beaconProxyFactory) == address(0) && beaconProxyFactory.beacon() == address(0)
36+
) {
37+
revert BeaconNotDeployed();
38+
}
39+
40+
bytes32 userSalt = _getUserSalt(token);
41+
vault = beaconProxyFactory.createProxy(userSalt);
2442

2543
IERC20Metadata tokenMetadata = IERC20Metadata(token);
2644
string memory name = string(abi.encodePacked("Master ", tokenMetadata.name()));
2745
string memory symbol = string(abi.encodePacked("m", tokenMetadata.symbol()));
2846

29-
bytes memory bytecode = abi.encodePacked(
30-
type(MasterVault).creationCode,
31-
abi.encode(token, name, symbol)
32-
);
33-
34-
vault = Create2.deploy(0, bytes32(0), bytecode);
47+
MasterVault(vault).initialize(IERC20(token), name, symbol, address(this));
3548

3649
emit VaultDeployed(token, vault);
3750
}
3851

39-
function calculateVaultAddress(address token) public view returns (address) {
40-
IERC20Metadata tokenMetadata = IERC20Metadata(token);
41-
string memory name = string(abi.encodePacked("Master ", tokenMetadata.name()));
42-
string memory symbol = string(abi.encodePacked("m", tokenMetadata.symbol()));
52+
function _getUserSalt(address token) internal pure returns (bytes32) {
53+
return keccak256(abi.encode(token));
54+
}
4355

44-
bytes32 bytecodeHash = keccak256(
45-
abi.encodePacked(
46-
type(MasterVault).creationCode,
47-
abi.encode(token, name, symbol)
48-
)
49-
);
50-
return Create2.computeAddress(bytes32(0), bytecodeHash);
56+
function calculateVaultAddress(address token) public view returns (address) {
57+
bytes32 userSalt = _getUserSalt(token);
58+
return beaconProxyFactory.calculateExpectedAddress(address(this), userSalt);
5159
}
5260

5361
function getVault(address token) external returns (address) {
@@ -61,9 +69,10 @@ contract MasterVaultFactory is IMasterVaultFactory, OwnableUpgradeable {
6169
// todo: consider a method to enable bridge owner to transfer specific master vault ownership to new address
6270
function setSubVault(
6371
address masterVault,
64-
address subVault
72+
address subVault,
73+
uint256 minSubVaultExchRateWad
6574
) external onlyOwner {
66-
IMasterVault(masterVault).setSubVault(subVault);
75+
IMasterVault(masterVault).setSubVault(subVault, minSubVaultExchRateWad);
6776
emit SubVaultSet(masterVault, subVault);
6877
}
69-
}
78+
}

0 commit comments

Comments
 (0)