-
Notifications
You must be signed in to change notification settings - Fork 156
another master vault design #125
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/ybb-master-vault
Are you sure you want to change the base?
Conversation
// maybe a simpler and more robust implementation would be for the owner to adjust the subVaultExchRateWad directly | ||
// this would also avoid the need for totalPrincipal tracking | ||
// however, this would require more trust in the owner | ||
uint256 public performanceFeeBps; // in basis points, e.g. 200 = 2% | todo a way to set this |
Check failure
Code scanning / Slither
Uninitialized state variables High
- MasterVault2._withdraw(address,address,address,uint256,uint256)
function _deposit( | ||
address caller, | ||
address receiver, | ||
uint256 assets, | ||
uint256 shares | ||
) internal virtual override { | ||
super._deposit(caller, receiver, assets, shares); | ||
totalPrincipal += assets; | ||
ERC4626 _subVault = subVault; | ||
if (address(_subVault) != address(0)) { | ||
_subVault.deposit(assets, address(this)); | ||
} | ||
} |
Check warning
Code scanning / Slither
Unused return Medium
function _withdraw( | ||
address caller, | ||
address receiver, | ||
address _owner, | ||
uint256 assets, | ||
uint256 shares | ||
) internal virtual override { | ||
ERC4626 _subVault = subVault; | ||
if (address(_subVault) != address(0)) { | ||
_subVault.withdraw(assets, address(this), address(this)); | ||
} | ||
|
||
////// PERF FEE STUFF ////// | ||
// determine profit portion and principal portion of assets | ||
uint256 _totalProfit = totalProfit(); | ||
// use shares because they are rounded up vs assets which are rounded down | ||
uint256 profitPortion = shares.mulDiv(_totalProfit, totalSupply(), Math.Rounding.Up); | ||
uint256 principalPortion = assets - profitPortion; | ||
|
||
// subtract principal portion from totalPrincipal | ||
totalPrincipal -= principalPortion; | ||
|
||
// send fee to owner | ||
if (performanceFeeBps > 0 && profitPortion > 0) { | ||
uint256 fee = profitPortion.mulDiv(performanceFeeBps, 10000, Math.Rounding.Up); | ||
// send fee to owner | ||
IERC20(asset()).safeTransfer(owner(), fee); | ||
|
||
// note subtraction | ||
assets -= fee; | ||
} | ||
|
||
////// END PERF FEE STUFF ////// | ||
|
||
// call super._withdraw with remaining assets | ||
super._withdraw(caller, receiver, _owner, assets, shares); | ||
} |
Check warning
Code scanning / Slither
Unused return Medium
totalPrincipal += assets; | ||
ERC4626 _subVault = subVault; | ||
if (address(_subVault) != address(0)) { | ||
_subVault.deposit(assets, address(this)); |
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.
nice one!
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.
thanks!
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.
LGTM! I like that you leveraged the built-in internal methods to supply deposits and withdrawals even if sub vault isn't set yet.
re PERF FEE: is it part of the requirement?
thanks! the requirement is that we have a switch for 100% or 0% performance fee, so may as well just make it variable between 0 and 100 🤷 |
function setSubVault(ERC4626 _subVault, uint256 minSubVaultExchRateWad) external onlyOwner { | ||
require(address(subVault) == address(0), "subvault already set"); | ||
require(address(_subVault) != address(0), "subvault cannot be zero address"); | ||
require(totalSupply() > 0, "must have supply before setting subvault"); | ||
require(address(_subVault.asset()) == address(asset()), "subvault asset mismatch"); | ||
|
||
// deposit to subvault | ||
IERC20(asset()).safeApprove(address(_subVault), type(uint256).max); | ||
uint256 subShares = _subVault.deposit(totalAssets(), address(this)); | ||
|
||
// set new exchange rate | ||
uint256 _subVaultExchRateWad = subShares.mulDiv(1e18, totalSupply(), Math.Rounding.Down); // todo: confirm rounding direction | ||
require(_subVaultExchRateWad >= minSubVaultExchRateWad, "subvault exchange rate too low"); | ||
subVaultExchRateWad = _subVaultExchRateWad; | ||
|
||
// set subvault | ||
subVault = _subVault; | ||
|
||
emit SubvaultChanged(address(0), address(_subVault)); | ||
} |
Check warning
Code scanning / Slither
Reentrancy vulnerabilities Medium
External calls:
- IERC20(asset()).safeApprove(address(_subVault),type()(uint256).max)
- subShares = _subVault.deposit(totalAssets(),address(this))
State variables written after the call(s):
- subVault = _subVault
MasterVault2.subVault can be used in cross function reentrancies:
- MasterVault2._convertToAssets(uint256,Math.Rounding)
- MasterVault2._convertToShares(uint256,Math.Rounding)
- MasterVault2._deposit(address,address,uint256,uint256)
- MasterVault2._withdraw(address,address,address,uint256,uint256)
- MasterVault2.maxDeposit(address)
- MasterVault2.maxMint(address)
- MasterVault2.setSubVault(ERC4626,uint256)
- MasterVault2.subVault
- MasterVault2.switchSubVault(ERC4626,uint256,uint256)
- MasterVault2.totalAssets()
function switchSubVault(ERC4626 newSubVault, uint256 minAssetExchRateWad, uint256 minNewSubVaultExchRateWad) external onlyOwner { | ||
ERC4626 oldSubVault = subVault; | ||
require(address(oldSubVault) != address(0), "no existing subvault"); | ||
if (address(newSubVault) != address(0)) { | ||
require(totalSupply() > 0, "must have supply before switching subvault"); | ||
require(address(newSubVault.asset()) == address(asset()), "subvault asset mismatch"); | ||
} | ||
|
||
// withdraw from old subvault | ||
uint256 _totalSupply = totalSupply(); | ||
uint256 assetReceived = oldSubVault.withdraw(oldSubVault.maxWithdraw(address(this)), address(this), address(this)); | ||
uint256 effectiveAssetExchRateWad = assetReceived.mulDiv(1e18, _totalSupply, Math.Rounding.Down); // todo: confirm rounding direction | ||
require(effectiveAssetExchRateWad >= minAssetExchRateWad, "too few assets received"); | ||
|
||
// revoke approval from old subvault | ||
IERC20(asset()).safeApprove(address(oldSubVault), 0); | ||
|
||
// deposit to new subvault | ||
if (address(newSubVault) != address(0)) { | ||
IERC20(asset()).safeApprove(address(newSubVault), type(uint256).max); | ||
uint256 newSubShares = newSubVault.deposit(totalAssets(), address(this)); | ||
|
||
// set new exchange rate | ||
uint256 _subVaultExchRateWad = newSubShares.mulDiv(1e18, totalSupply(), Math.Rounding.Down); | ||
require(_subVaultExchRateWad >= minNewSubVaultExchRateWad, "new subvault exchange rate too low"); | ||
subVaultExchRateWad = _subVaultExchRateWad; | ||
} | ||
else { | ||
subVaultExchRateWad = 1e18; | ||
} | ||
|
||
// set subvault | ||
subVault = newSubVault; | ||
|
||
emit SubvaultChanged(address(oldSubVault), address(newSubVault)); | ||
} |
Check warning
Code scanning / Slither
Reentrancy vulnerabilities Medium
External calls:
- assetReceived = oldSubVault.withdraw(oldSubVault.maxWithdraw(address(this)),address(this),address(this))
- IERC20(asset()).safeApprove(address(oldSubVault),0)
- IERC20(asset()).safeApprove(address(newSubVault),type()(uint256).max)
- newSubShares = newSubVault.deposit(totalAssets(),address(this))
State variables written after the call(s):
- subVault = newSubVault
MasterVault2.subVault can be used in cross function reentrancies:
- MasterVault2._convertToAssets(uint256,Math.Rounding)
- MasterVault2._convertToShares(uint256,Math.Rounding)
- MasterVault2._deposit(address,address,uint256,uint256)
- MasterVault2._withdraw(address,address,address,uint256,uint256)
- MasterVault2.maxDeposit(address)
- MasterVault2.maxMint(address)
- MasterVault2.setSubVault(ERC4626,uint256)
- MasterVault2.subVault
- MasterVault2.switchSubVault(ERC4626,uint256,uint256)
- MasterVault2.totalAssets()
added a commit to change the way the owner specifies slippage tolerance when setting the subvault. it's an exchange rate based tolerance now which is safer against frontrunning. idea being that the owner doesn't know how many shares/assets the mastervault will have when their |
this version should be robust to changes in the subvault's exchange rate.
it should be more robust to slippage (ie on an LP strategy)
the performance fee, however, is not robust to MEV