-
Notifications
You must be signed in to change notification settings - Fork 156
MasterVault as ERC4626 #124
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
Closed
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,73 +1,170 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import "./IMasterVault.sol"; | ||
import "@openzeppelin/contracts/access/Ownable.sol"; | ||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; | ||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
import "@openzeppelin/contracts/utils/math/Math.sol"; | ||
import { IERC4626 } from "lib/forge-std/src/interfaces/IERC4626.sol"; | ||
import { ERC4626 } from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; | ||
|
||
// todo: make this more like a 4626 vault, erc20 shares + deposit + withdraw | ||
// todo: consider beacon proxy | ||
contract MasterVault is IMasterVault, Ownable { | ||
// todo: should we add role based access control? | ||
contract MasterVault is ERC4626, Ownable { | ||
using SafeERC20 for IERC20; | ||
|
||
error CallerIsNotGateway(); | ||
error ZeroAddress(); | ||
error SubVaultIsNotSet(); | ||
error InsufficientAssets(); | ||
error InsufficientShares(); | ||
error InsufficientYield(); | ||
|
||
address public immutable token; | ||
address public immutable gateway; | ||
address public immutable underlyingAsset; | ||
address public subVault; | ||
uint256 public netDeposits; | ||
|
||
event Deposited(uint256 amount); | ||
event Withdrawn(uint256 amount, address recipient, uint256 shares); | ||
event SubVaultSet(address subVault); | ||
event YieldWithdrawn(address indexed owner, uint256 amount); | ||
|
||
modifier onlyGateway() { | ||
if (msg.sender != gateway) { | ||
revert CallerIsNotGateway(); | ||
constructor( | ||
address _underlyingAsset, | ||
address _subVault | ||
) ERC20( | ||
string(abi.encodePacked("Wrapped ", IERC20Metadata(_underlyingAsset).name())), | ||
string(abi.encodePacked("mst", IERC20Metadata(_underlyingAsset).symbol())) | ||
) ERC4626(IERC20(_underlyingAsset)) Ownable() { | ||
if (_underlyingAsset == address(0)) { | ||
revert ZeroAddress(); | ||
} | ||
_; | ||
} | ||
|
||
// todo: remove gateway and owner params | ||
// factory retains ownership. anyone can call deposit and withdraw since it's close to a standard 4626 vault | ||
constructor(address _token, address _gateway, address _owner) Ownable() { | ||
if (_token == address(0) || _gateway == address(0) || _owner == address(0)) { | ||
if (_subVault == address(0)) { | ||
revert ZeroAddress(); | ||
} | ||
token = _token; | ||
gateway = _gateway; | ||
transferOwnership(_owner); | ||
underlyingAsset = _underlyingAsset; | ||
_setSubVault(_subVault); | ||
} | ||
|
||
function deposit( | ||
uint256 amount | ||
) external override onlyGateway returns (uint256 amountDeposited) { | ||
function _setSubVault(address _subVault) internal { | ||
subVault = _subVault; | ||
emit SubVaultSet(_subVault); | ||
} | ||
|
||
/** @dev See {IERC4626-deposit}. */ | ||
function deposit(uint256 assets, address receiver) public virtual override returns (uint256 shares) { | ||
if (subVault == address(0)) { | ||
revert SubVaultIsNotSet(); | ||
} | ||
amountDeposited = IERC4626(subVault).deposit(amount, gateway); | ||
emit Deposited(amount); | ||
|
||
IERC20(underlyingAsset).safeTransferFrom(msg.sender, address(this), assets); | ||
IERC20(underlyingAsset).safeIncreaseAllowance(subVault, assets); | ||
IERC4626(subVault).deposit(assets, address(this)); | ||
shares = convertToShares(assets); | ||
netDeposits += assets; | ||
_mint(receiver, shares); | ||
emit Deposit(msg.sender, receiver, assets, shares); | ||
} | ||
|
||
/** @dev See {IERC4626-mint}. */ | ||
function mint(uint256 shares, address receiver) public virtual override returns (uint256 assets) { | ||
assets = convertToAssets(shares); | ||
deposit(assets, receiver); | ||
} | ||
|
||
function withdraw(uint256 amount, address recipient) external override onlyGateway { | ||
/** @dev See {IERC4626-withdraw}. */ | ||
function withdraw(uint256 assets, address receiver, address owner) public virtual override returns (uint256 shares) { | ||
if (subVault == address(0)) { | ||
revert SubVaultIsNotSet(); | ||
} | ||
uint256 shares = IERC4626(subVault).withdraw(amount, recipient, gateway); | ||
emit Withdrawn(amount, recipient, shares); | ||
|
||
shares = convertToShares(assets); | ||
|
||
if (msg.sender != owner) { | ||
_spendAllowance(owner, msg.sender, shares); | ||
} | ||
|
||
uint256 subVaultAssets = _calculateSubVaultWithdrawal(assets); | ||
IERC4626(subVault).withdraw(subVaultAssets, address(this), address(this)); | ||
_burn(owner, shares); | ||
netDeposits -= assets; | ||
IERC20(underlyingAsset).safeTransfer(receiver, assets); | ||
emit Withdraw(msg.sender, receiver, owner, assets, shares); | ||
} | ||
Comment on lines
+77
to
+94
Check warningCode scanning / Slither Unused return Medium |
||
|
||
/** @dev See {IERC4626-redeem}. */ | ||
function redeem(uint256 shares, address receiver, address owner) public virtual override returns (uint256 assets) { | ||
assets = convertToAssets(shares); | ||
withdraw(assets, receiver, owner); | ||
} | ||
|
||
/// @notice Returns the address of the sub-vault that holds the underlying assets | ||
/// @return The address of the ERC4626 sub-vault | ||
function getSubVault() external view returns (address) { | ||
return subVault; | ||
} | ||
|
||
function setSubVault(address _subVault) external override onlyOwner { | ||
// todo: need to make sure we transfer funds here | ||
subVault = _subVault; | ||
emit SubVaultSet(_subVault); | ||
/// @notice Sets a new sub-vault address (only owner) | ||
/// @param _subVault The new ERC4626 sub-vault address | ||
function setSubVault(address _subVault) external onlyOwner { | ||
_setSubVault(_subVault); | ||
} | ||
|
||
/// @notice Returns the net amount of underlying assets deposited by users | ||
/// @return The total deposits minus withdrawals | ||
function getNetDeposits() external view returns (uint256) { | ||
return netDeposits; | ||
} | ||
|
||
function _calculateSubVaultWithdrawal(uint256 assets) internal view returns (uint256) { | ||
return assets; | ||
} | ||
|
||
/** @dev See {IERC4626-convertToShares}. */ | ||
function convertToShares(uint256 assets) public view virtual override returns (uint256) { | ||
return assets; | ||
} | ||
|
||
/** @dev See {IERC4626-convertToAssets}. */ | ||
function convertToAssets(uint256 shares) public view virtual override returns (uint256) { | ||
return shares; | ||
} | ||
|
||
/** @dev See {IERC4626-totalAssets}. */ | ||
function totalAssets() public view virtual override returns (uint256) { | ||
if (subVault == address(0)) { | ||
return 0; | ||
} | ||
return IERC4626(subVault).totalAssets(); | ||
} | ||
|
||
/// @notice Calculates the current yield (profit) available for withdrawal by owner | ||
/// @return yield The amount of yield (totalAssets - netDeposits) | ||
function getYield() public view returns (uint256 yield) { | ||
uint256 total = totalAssets(); | ||
yield = total > netDeposits ? total - netDeposits : 0; | ||
} | ||
|
||
/// @notice Withdraws available yield to the owner | ||
/// @dev Only callable by owner, handles liquidity constraints automatically | ||
function withdrawYield() external onlyOwner { | ||
if (subVault == address(0)) { | ||
revert SubVaultIsNotSet(); | ||
} | ||
|
||
uint256 yield = getYield(); | ||
if (yield == 0) { | ||
revert InsufficientYield(); | ||
} | ||
|
||
uint256 maxWithdrawable = IERC4626(subVault).maxWithdraw(address(this)); | ||
if (yield > maxWithdrawable) { | ||
yield = maxWithdrawable; | ||
} | ||
|
||
IERC4626(subVault).withdraw(yield, address(this), address(this)); | ||
IERC20(underlyingAsset).safeTransfer(msg.sender, yield); | ||
emit YieldWithdrawn(msg.sender, yield); | ||
} | ||
Check warningCode scanning / Slither Unused return Medium
MasterVault.withdrawYield() ignores return value by IERC4626(subVault).withdraw(yield,address(this),address(this))
|
||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check warning
Code scanning / Slither
Unused return Medium