Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions contracts/tokenbridge/libraries/vault/IMasterVaultFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ pragma solidity ^0.8.0;

interface IMasterVaultFactory {
event VaultDeployed(address indexed token, address indexed vault);
event SubVaultSet(address indexed masterVault, address indexed subVault);

function initialize(address _owner) external;
function deployVault(address token) external returns (address vault);
function calculateVaultAddress(address token) external view returns (address);
function getVault(address token) external returns (address);
function setSubVault(address masterVault, address subVault, uint256 minSubVaultExchRateWad) external;
}
50 changes: 38 additions & 12 deletions contracts/tokenbridge/libraries/vault/MasterVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@
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 {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {MathUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract MasterVault is Initializable, ERC4626Upgradeable, OwnableUpgradeable {
contract MasterVault is Initializable, ERC4626Upgradeable, AccessControlUpgradeable, PausableUpgradeable {
using SafeERC20 for IERC20;
using MathUpgradeable for uint256;

bytes32 public constant VAULT_MANAGER_ROLE = keccak256("VAULT_MANAGER_ROLE");
bytes32 public constant FEE_MANAGER_ROLE = keccak256("FEE_MANAGER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

error TooFewSharesReceived();
error TooManySharesBurned();
error TooManyAssetsDeposited();
Expand Down Expand Up @@ -61,7 +66,17 @@

__ERC20_init(_name, _symbol);
__ERC4626_init(IERC20Upgradeable(address(_asset)));
_transferOwnership(_owner);
__AccessControl_init();
__Pausable_init();

_setRoleAdmin(VAULT_MANAGER_ROLE, DEFAULT_ADMIN_ROLE);
_setRoleAdmin(FEE_MANAGER_ROLE, DEFAULT_ADMIN_ROLE);
_setRoleAdmin(PAUSER_ROLE, DEFAULT_ADMIN_ROLE);

_grantRole(DEFAULT_ADMIN_ROLE, _owner);
_grantRole(VAULT_MANAGER_ROLE, _owner);
_grantRole(FEE_MANAGER_ROLE, _owner); // todo: consider permissionless by default
_grantRole(PAUSER_ROLE, _owner);

subVaultExchRateWad = 1e18;
}
Expand Down Expand Up @@ -94,14 +109,14 @@
/// @notice Set a subvault. Can only be called if there is not already a subvault set.
/// @param _subVault The subvault to set. Must be an ERC4626 vault with the same asset as this MasterVault.
/// @param minSubVaultExchRateWad Minimum acceptable ratio (times 1e18) of new subvault shares to outstanding MasterVault shares after deposit.
function setSubVault(IERC4626 _subVault, uint256 minSubVaultExchRateWad) external onlyOwner {
function setSubVault(IERC4626 _subVault, uint256 minSubVaultExchRateWad) external onlyRole(VAULT_MANAGER_ROLE) {
if (address(subVault) != address(0)) revert SubVaultAlreadySet();
_setSubVault(_subVault, minSubVaultExchRateWad);
}

/// @notice Revokes the current subvault, moving all assets back to MasterVault
/// @param minAssetExchRateWad Minimum acceptable ratio (times 1e18) of assets received from subvault to outstanding MasterVault shares
function revokeSubVault(uint256 minAssetExchRateWad) external onlyOwner {
function revokeSubVault(uint256 minAssetExchRateWad) external onlyRole(VAULT_MANAGER_ROLE) {
_revokeSubVault(minAssetExchRateWad);
}

Expand Down Expand Up @@ -142,7 +157,7 @@
/// @param newSubVault The new subvault to switch to, or zero address to revoke current subvault
/// @param minAssetExchRateWad Minimum acceptable ratio (times 1e18) of assets received from old subvault to outstanding MasterVault shares
/// @param minNewSubVaultExchRateWad Minimum acceptable ratio (times 1e18) of new subvault shares to outstanding MasterVault shares after deposit
function switchSubVault(IERC4626 newSubVault, uint256 minAssetExchRateWad, uint256 minNewSubVaultExchRateWad) external onlyOwner {
function switchSubVault(IERC4626 newSubVault, uint256 minAssetExchRateWad, uint256 minNewSubVaultExchRateWad) external onlyRole(VAULT_MANAGER_ROLE) {
_revokeSubVault(minAssetExchRateWad);

if (address(newSubVault) != address(0)) {
Expand All @@ -160,35 +175,43 @@

/// @notice Toggle performance fee collection on/off
/// @param enabled True to enable performance fees, false to disable
function setPerformanceFee(bool enabled) external onlyOwner {
function setPerformanceFee(bool enabled) external onlyRole(VAULT_MANAGER_ROLE) {
enablePerformanceFee = enabled;
emit PerformanceFeeToggled(enabled);
}

/// @notice Set the beneficiary address for performance fees
/// @param newBeneficiary Address to receive performance fees, zero address defaults to owner
function setBeneficiary(address newBeneficiary) external onlyOwner {
function setBeneficiary(address newBeneficiary) external onlyRole(FEE_MANAGER_ROLE) {
address oldBeneficiary = beneficiary;
beneficiary = newBeneficiary;
emit BeneficiaryUpdated(oldBeneficiary, newBeneficiary);
}

/// @notice Withdraw all accumulated performance fees to beneficiary
/// @dev Only callable by owner when performance fees are enabled
function withdrawPerformanceFees() external onlyOwner {
/// @dev Only callable by fee manager when performance fees are enabled
function withdrawPerformanceFees() external onlyRole(FEE_MANAGER_ROLE) {
if (!enablePerformanceFee) revert PerformanceFeeDisabled();
if (beneficiary == address(0)) revert BeneficiaryNotSet();

uint256 totalProfits = totalProfit();
if (totalProfits > 0) {
IERC4626 _subVault = subVault;
if (address(_subVault) != address(0)) {
_subVault.withdraw(totalProfits, address(this), address(this));
}
IERC20(asset()).safeTransfer(beneficiary, totalProfits);
}
}

Check warning

Code scanning / Slither

Unused return Medium


function pause() external onlyRole(PAUSER_ROLE) {
_pause();
}

function unpause() external onlyRole(PAUSER_ROLE) {
_unpause();
}

/** @dev See {IERC4626-totalAssets}. */
function totalAssets() public view virtual override returns (uint256) {
IERC4626 _subVault = subVault;
Expand All @@ -208,6 +231,9 @@

/** @dev See {IERC4626-maxMint}. */
function maxMint(address) public view virtual override returns (uint256) {
if (address(subVault) == address(0)) {
return type(uint256).max;
}
uint256 subShares = subVault.maxMint(address(this));
if (subShares == type(uint256).max) {
return type(uint256).max;
Expand Down Expand Up @@ -255,7 +281,7 @@
address receiver,
uint256 assets,
uint256 shares
) internal virtual override {
) internal virtual override whenNotPaused {
super._deposit(caller, receiver, assets, shares);

totalPrincipal += assets;
Expand All @@ -274,7 +300,7 @@
address _owner,
uint256 assets,
uint256 shares
) internal virtual override {
) internal virtual override whenNotPaused {
totalPrincipal -= assets;

IERC4626 _subVault = subVault;
Expand Down
19 changes: 4 additions & 15 deletions contracts/tokenbridge/libraries/vault/MasterVaultFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,22 @@
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 {
contract MasterVaultFactory is IMasterVaultFactory, Initializable {
error ZeroAddress();
error BeaconNotDeployed();

BeaconProxyFactory public beaconProxyFactory;
address public owner;

function initialize(address _owner) public initializer {
_transferOwnership(_owner);

owner = _owner;
MasterVault masterVaultImplementation = new MasterVault();
UpgradeableBeacon beacon = new UpgradeableBeacon(address(masterVaultImplementation));
beaconProxyFactory = new BeaconProxyFactory();
Expand All @@ -44,7 +43,7 @@ contract MasterVaultFactory is IMasterVaultFactory, OwnableUpgradeable {
string memory name = string(abi.encodePacked("Master ", tokenMetadata.name()));
string memory symbol = string(abi.encodePacked("m", tokenMetadata.symbol()));

MasterVault(vault).initialize(IERC20(token), name, symbol, address(this));
MasterVault(vault).initialize(IERC20(token), name, symbol, owner);

emit VaultDeployed(token, vault);
}
Expand All @@ -65,14 +64,4 @@ contract MasterVaultFactory is IMasterVaultFactory, OwnableUpgradeable {
}
return vault;
}

// todo: consider a method to enable bridge owner to transfer specific master vault ownership to new address
function setSubVault(
address masterVault,
address subVault,
uint256 minSubVaultExchRateWad
) external onlyOwner {
IMasterVault(masterVault).setSubVault(subVault, minSubVaultExchRateWad);
emit SubVaultSet(masterVault, subVault);
}
}
Loading
Loading