-
Notifications
You must be signed in to change notification settings - Fork 156
Make master vault upgradable #127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: wa/master-vault-isolated
Are you sure you want to change the base?
Changes from 3 commits
7922536
6331e7a
5632d3d
2b8f151
5348604
3a24b45
db0feb1
1e0c267
4aad0dd
9ff2c39
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -1,16 +1,20 @@ | ||||||||
// SPDX-License-Identifier: Apache-2.0 | ||||||||
pragma solidity ^0.8.0; | ||||||||
|
||||||||
import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; | ||||||||
import { IERC20, ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||||||||
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; | ||||||||
import {ERC4626Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; | ||||||||
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; | ||||||||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||||||||
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; | ||||||||
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; | ||||||||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; | ||||||||
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; | ||||||||
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; | ||||||||
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; | ||||||||
import {MathUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol"; | ||||||||
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||||||||
|
||||||||
contract MasterVault is ERC4626, Ownable { | ||||||||
contract MasterVault is Initializable, ERC4626Upgradeable, OwnableUpgradeable { | ||||||||
using SafeERC20 for IERC20; | ||||||||
using Math for uint256; | ||||||||
using MathUpgradeable for uint256; | ||||||||
|
||||||||
error TooFewSharesReceived(); | ||||||||
error TooManySharesBurned(); | ||||||||
|
@@ -26,16 +30,21 @@ | |||||||
error NewSubVaultExchangeRateTooLow(); | ||||||||
error BeneficiaryNotSet(); | ||||||||
error PerformanceFeeDisabled(); | ||||||||
error NoSharesRedeemed(); | ||||||||
error NoSubvaultShares(); | ||||||||
error NoSharesBurned(); | ||||||||
error InvalidAsset(); | ||||||||
error InvalidOwner(); | ||||||||
|
||||||||
// todo: avoid inflation, rounding, other common 4626 vulns | ||||||||
// we may need a minimum asset or master share amount when setting subvaults (bc of exchange rate calc) | ||||||||
ERC4626 public subVault; | ||||||||
IERC4626 public subVault; | ||||||||
|
||||||||
// how many subVault shares one MV2 share can be redeemed for | ||||||||
// initially 1 to 1 | ||||||||
// constant per subvault | ||||||||
// changes when subvault is set | ||||||||
uint256 public subVaultExchRateWad = 1e18; | ||||||||
uint256 public subVaultExchRateWad; | ||||||||
|
||||||||
// note: the performance fee can be avoided if the underlying strategy can be sandwiched (eg ETH to wstETH dex swap) | ||||||||
// maybe a simpler and more robust implementation would be for the owner to adjust the subVaultExchRateWad directly | ||||||||
|
@@ -49,16 +58,27 @@ | |||||||
event PerformanceFeeToggled(bool enabled); | ||||||||
event BeneficiaryUpdated(address indexed oldBeneficiary, address indexed newBeneficiary); | ||||||||
|
||||||||
constructor(IERC20 _asset, string memory _name, string memory _symbol) ERC20(_name, _symbol) ERC4626(_asset) Ownable() {} | ||||||||
function vaultInit(IERC20 _asset, string memory _name, string memory _symbol, address _owner) external initializer { | ||||||||
if (address(_asset) == address(0)) revert InvalidAsset(); | ||||||||
if (_owner == address(0)) revert InvalidOwner(); | ||||||||
|
||||||||
__ERC20_init(_name, _symbol); | ||||||||
__ERC4626_init(IERC20Upgradeable(address(_asset))); | ||||||||
__Ownable_init(); | ||||||||
|
__Ownable_init(); |
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we leave this alone except for changing Math
to MathUpgradeable
? what is the purpose of the changes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this include a bug for a race condition which cause this line to fail:
uint256 _subVaultExchRateWad = subShares.mulDiv(1e18, totalSupply(), Math.Rounding.Down);
that's not related to the upgradability but the issue is that this line will always fail with error "division by zero" because of totalSupply() returns zero after (master vault deposited all funds into new subvault
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think an easy fix would be to move subVault = _subVault;
in between deposit and _subVaultExchRateWad calculation. just pushed a fix for that.
Check warning
Code scanning / Slither
Dangerous strict equalities Medium
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is the purpose of the changes here?
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's leave this as is since it's not relevant to upgradeability
uint256 sharesRedeemed = _subVault.withdraw(totalProfits, address(this), address(this)); | |
if (sharesRedeemed == 0) revert NoSharesRedeemed(); | |
_subVault.withdraw(totalProfits, address(this), address(this)); |
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the rationale behind this check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's just to satisfy slither as there is a rule to not have unused variables / unused returns
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok i think we can ignore slither
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've reverted these changes to cover slither with different PR
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@godzillaba same here
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,49 +5,58 @@ pragma solidity ^0.8.0; | |
import "@openzeppelin/contracts/utils/Create2.sol"; | ||
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; | ||
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; | ||
import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; | ||
import "../ClonableBeaconProxy.sol"; | ||
import "./IMasterVault.sol"; | ||
import "./IMasterVaultFactory.sol"; | ||
import "./MasterVault.sol"; | ||
|
||
contract MasterVaultFactory is IMasterVaultFactory, OwnableUpgradeable { | ||
|
||
error ZeroAddress(); | ||
error BeaconNotDeployed(); | ||
|
||
UpgradeableBeacon public beacon; | ||
|
||
BeaconProxyFactory public beaconProxyFactory; | ||
|
||
function initialize(address _owner) public initializer { | ||
_transferOwnership(_owner); | ||
|
||
MasterVault masterVaultImplementation = new MasterVault(); | ||
beacon = new UpgradeableBeacon(address(masterVaultImplementation)); | ||
|
||
beaconProxyFactory = new BeaconProxyFactory(); | ||
beaconProxyFactory.initialize(address(beacon)); | ||
beacon.transferOwnership(_owner); | ||
} | ||
|
||
function deployVault(address token) public returns (address vault) { | ||
if (token == address(0)) { | ||
revert ZeroAddress(); | ||
} | ||
if ( | ||
address(beaconProxyFactory) == address(0) && beaconProxyFactory.beacon() == address(0) | ||
) { | ||
revert BeaconNotDeployed(); | ||
} | ||
|
||
bytes32 userSalt = _getUserSalt(token); | ||
vault = beaconProxyFactory.createProxy(userSalt); | ||
|
||
IERC20Metadata tokenMetadata = IERC20Metadata(token); | ||
string memory name = string(abi.encodePacked("Master ", tokenMetadata.name())); | ||
string memory symbol = string(abi.encodePacked("m", tokenMetadata.symbol())); | ||
|
||
bytes memory bytecode = abi.encodePacked( | ||
type(MasterVault).creationCode, | ||
abi.encode(token, name, symbol) | ||
); | ||
|
||
vault = Create2.deploy(0, bytes32(0), bytecode); | ||
MasterVault(vault).vaultInit(IERC20(token), name, symbol, address(this)); | ||
|
||
|
||
emit VaultDeployed(token, vault); | ||
} | ||
|
||
function calculateVaultAddress(address token) public view returns (address) { | ||
IERC20Metadata tokenMetadata = IERC20Metadata(token); | ||
string memory name = string(abi.encodePacked("Master ", tokenMetadata.name())); | ||
string memory symbol = string(abi.encodePacked("m", tokenMetadata.symbol())); | ||
function _getUserSalt(address token) internal pure returns (bytes32) { | ||
return keccak256(abi.encode(token)); | ||
} | ||
|
||
bytes32 bytecodeHash = keccak256( | ||
abi.encodePacked( | ||
type(MasterVault).creationCode, | ||
abi.encode(token, name, symbol) | ||
) | ||
); | ||
return Create2.computeAddress(bytes32(0), bytecodeHash); | ||
function calculateVaultAddress(address token) public view returns (address) { | ||
bytes32 userSalt = _getUserSalt(token); | ||
return beaconProxyFactory.calculateExpectedAddress(address(this), userSalt); | ||
} | ||
|
||
function getVault(address token) external returns (address) { | ||
|
@@ -61,9 +70,10 @@ contract MasterVaultFactory is IMasterVaultFactory, OwnableUpgradeable { | |
// todo: consider a method to enable bridge owner to transfer specific master vault ownership to new address | ||
function setSubVault( | ||
address masterVault, | ||
address subVault | ||
address subVault, | ||
uint256 minSubVaultExchRateWad | ||
) external onlyOwner { | ||
IMasterVault(masterVault).setSubVault(subVault); | ||
IMasterVault(masterVault).setSubVault(subVault, minSubVaultExchRateWad); | ||
emit SubVaultSet(masterVault, subVault); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit, for convention