Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
130 commits
Select commit Hold shift + click to select a range
951c33c
feat: add master vault
waelsy123 Sep 29, 2025
fc89964
permission withdraw performence fees
waelsy123 Sep 29, 2025
7922536
feat: make master vault beacon upgradable
waelsy123 Oct 1, 2025
6331e7a
fixup! feat: make master vault beacon upgradable
waelsy123 Oct 3, 2025
5632d3d
satisify slither
waelsy123 Oct 3, 2025
2b8f151
remove unnecessary checks
waelsy123 Oct 16, 2025
5348604
fixup! feat: make master vault beacon upgradable
waelsy123 Oct 16, 2025
3a24b45
fix: set subvault in between depsoit and _subVaultExchRateWad calcula…
waelsy123 Oct 16, 2025
db0feb1
fixup! feat: make master vault beacon upgradable
waelsy123 Oct 19, 2025
1e0c267
fixup! feat: make master vault beacon upgradable
waelsy123 Oct 19, 2025
4aad0dd
fix: remove not used beacon address from storage
waelsy123 Oct 19, 2025
9ff2c39
fixup! feat: make master vault beacon upgradable
waelsy123 Oct 19, 2025
e9d49e5
feat: make master vault upgradable
waelsy123 Oct 22, 2025
cd1a25d
feat: access control roles (#132)
waelsy123 Nov 7, 2025
72956ed
add MasterVault contract natspec
godzillaba Nov 10, 2025
35c2a3c
remove subVaultExchRateWad
godzillaba Nov 10, 2025
9b4809c
remove unused errors
godzillaba Nov 10, 2025
a05eddc
remove switchSubVault
godzillaba Nov 10, 2025
fc74ddd
Merge branch 'ha/mv-remove-switch-func' into ha/mv-docs
godzillaba Nov 10, 2025
302d590
role comment
godzillaba Nov 10, 2025
bd1dffb
Merge pull request #133 from OffchainLabs/ha/mv-remove-stored-exch-rate
godzillaba Nov 12, 2025
724abf5
Merge pull request #134 from OffchainLabs/ha/mv-remove-switch-func
godzillaba Nov 12, 2025
cc04f52
document roles
godzillaba Nov 12, 2025
b37aec9
add fee requirement
godzillaba Nov 12, 2025
4470279
wip: continuous fee accounting
godzillaba Nov 12, 2025
9932afd
Revert "add fee requirement"
godzillaba Nov 12, 2025
78b172d
Merge branch 'ha/mv-docs' into ha/mv-fix-perf-fee-principal-tracking
godzillaba Nov 12, 2025
cb72759
wip continuous accounting
godzillaba Dec 1, 2025
fe0d73b
finish continuous accounting
godzillaba Dec 3, 2025
bc27b40
rearrange
godzillaba Dec 5, 2025
6b68210
Merge pull request #135 from OffchainLabs/ha/mv-docs
godzillaba Dec 8, 2025
76fccff
Merge pull request #136 from OffchainLabs/ha/mv-fix-perf-fee-principa…
godzillaba Dec 8, 2025
e225136
remove unused errors
godzillaba Dec 8, 2025
eb26c62
avoid principal underflow
godzillaba Dec 8, 2025
a376e34
Merge pull request #139 from OffchainLabs/ha/fix-perf-fee-underflow
godzillaba Dec 9, 2025
cf71c56
tests: core functionality (#140)
waelsy123 Dec 23, 2025
dc5c3a3
fuzz test sub vault double in assets (#143)
waelsy123 Dec 23, 2025
f4a9ea6
tests: move util methods into core MasterVaultCoreTests contract
waelsy123 Jan 3, 2026
6393094
test: add test_scenario01_noGainNoLoss
waelsy123 Jan 4, 2026
50cb6e4
test: add test_scenario02_socializeLosses & test_scenario03_profitToB…
waelsy123 Jan 4, 2026
f9c6a61
test: add test_scenario04_secondDepositAfterFeeClaim
waelsy123 Jan 4, 2026
26b6907
test: add test_scenario05_profitThenLoss & test_scenario06_profitClai…
waelsy123 Jan 4, 2026
8a850ec
test: add test_scenario07_afterLossNewDeposit
waelsy123 Jan 4, 2026
b01306b
test: add test_scenario08_depositAfterLoss
waelsy123 Jan 4, 2026
1f888a5
test: add MasterVaultScenarioCoreTest and its util methods
waelsy123 Jan 7, 2026
a299f38
Merge pull request #147 from OffchainLabs/cleanup-tests
godzillaba Jan 7, 2026
9cf303e
account for capacity constraints during rebalance
godzillaba Jan 7, 2026
c8c05a7
minimum rebalance amount
godzillaba Jan 7, 2026
ddfc513
set minimum rebalance amount
godzillaba Jan 7, 2026
5c55f23
rename variable
godzillaba Jan 7, 2026
1dd80c7
check actual amount instead of desired amount
godzillaba Jan 7, 2026
c95c99f
clean up modifiers
godzillaba Jan 7, 2026
b257cad
update comment
godzillaba Jan 7, 2026
24f6d96
Merge pull request #149 from OffchainLabs/ha/mv-rebalance-improvements
godzillaba Jan 7, 2026
b5fd393
clamp _totalAssetsLessProfit
godzillaba Jan 6, 2026
5af3e53
feat: rebalance with `KEEPER` (#151)
waelsy123 Jan 12, 2026
d986bb0
replace ERC4626 inheritance with ERC20
godzillaba Jan 12, 2026
c9dcc07
remove maxmint and maxdeposit
godzillaba Jan 12, 2026
71fc5b0
external deposit and redeem
godzillaba Jan 12, 2026
f98c9de
add note about rounding direction
godzillaba Jan 13, 2026
3f83a2d
Merge pull request #152 from OffchainLabs/ha/mv-ditch-4626
waelsy123 Jan 13, 2026
21ab1f7
add onlyGateway modifier
waelsy123 Jan 13, 2026
8024439
fixup! add onlyGateway modifier
waelsy123 Jan 13, 2026
b90ec63
Merge pull request #153 from OffchainLabs/ybb/gateway-role
waelsy123 Jan 13, 2026
ec8f4a9
ybb: whitelist subvaults (#154)
waelsy123 Jan 13, 2026
660358b
split roles into separate contract
godzillaba Jan 13, 2026
64ec041
Merge branch 'wa/master-vault-isolated' into ha/mv-roles-registry
godzillaba Jan 13, 2026
5b3f52f
add external roles registry
godzillaba Jan 13, 2026
fb81950
doc
godzillaba Jan 13, 2026
3a1b986
document roles
godzillaba Jan 13, 2026
b35455b
Merge pull request #156 from OffchainLabs/ha/mv-roles-overhaul
waelsy123 Jan 13, 2026
000d61c
fix role admin overwriting
godzillaba Jan 14, 2026
fd759ff
redundant init
godzillaba Jan 14, 2026
9b4365d
Merge pull request #155 from OffchainLabs/ha/mv-roles-registry
waelsy123 Jan 14, 2026
cf7dbe8
redeem internal -> external
godzillaba Jan 14, 2026
b79da31
fix role admin overwriting 2
godzillaba Jan 14, 2026
234f81d
Merge pull request #157 from OffchainLabs/ha/mv-roles-fix
waelsy123 Jan 14, 2026
2157058
set cooldown period in seconds for rebalancing
waelsy123 Jan 14, 2026
a876fd6
move lastRebalanceTime update
godzillaba Jan 14, 2026
0d12372
add default of 1
godzillaba Jan 14, 2026
dcc5ac4
Revert "move lastRebalanceTime update"
godzillaba Jan 14, 2026
6d4f011
Merge pull request #159 from OffchainLabs/ha/cooldown-review
waelsy123 Jan 14, 2026
25a0864
Merge pull request #158 from OffchainLabs/ybb/cooldown
waelsy123 Jan 14, 2026
a91da02
remove unused rounding params
godzillaba Jan 14, 2026
65ac084
confirm no fee req
godzillaba Jan 14, 2026
b1b8173
remove arbitrary call feature
godzillaba Jan 14, 2026
ec7120f
remove old todo about 4626 vulns
godzillaba Jan 14, 2026
8fd63ca
docs
godzillaba Jan 14, 2026
44e6ddf
fmt
godzillaba Jan 14, 2026
1343057
Merge branch 'ha/mv-tighten-rounding' into ha/mv-docs
godzillaba Jan 14, 2026
d42323a
principalPriceWad
godzillaba Jan 14, 2026
38c3149
more docs
godzillaba Jan 14, 2026
ca4a895
Merge branch 'ha/mv-docs' into ha/safer-principal-tracking
godzillaba Jan 14, 2026
8f21eb0
more docs
godzillaba Jan 14, 2026
44918d3
Merge branch 'ha/mv-docs' into ha/safer-principal-tracking
godzillaba Jan 14, 2026
b39016e
fmt
godzillaba Jan 14, 2026
3cdc7a4
remove internal _rebalance
godzillaba Jan 15, 2026
e4bc23e
never skip rebalancing, always revert
godzillaba Jan 15, 2026
d7dbb22
fmt
godzillaba Jan 15, 2026
54a15c3
document extra decimals better
godzillaba Jan 15, 2026
c2a6b44
Merge branch 'ha/safer-principal-tracking' into ha/rebalance-small-re…
godzillaba Jan 15, 2026
a6251f0
make _totalPrincipal internal
godzillaba Jan 15, 2026
528d704
Merge branch 'ha/safer-principal-tracking' into ha/rebalance-small-re…
godzillaba Jan 15, 2026
34ae58b
document _totalPrincipal
godzillaba Jan 15, 2026
485383e
Merge branch 'ha/safer-principal-tracking' into ha/rebalance-small-re…
godzillaba Jan 15, 2026
6f85180
Merge pull request #163 from OffchainLabs/ha/rebalance-small-refactor
waelsy123 Jan 15, 2026
610870c
Merge pull request #162 from OffchainLabs/ha/safer-principal-tracking
waelsy123 Jan 15, 2026
a3db478
Merge pull request #161 from OffchainLabs/ha/mv-docs
waelsy123 Jan 15, 2026
f89d010
Merge pull request #160 from OffchainLabs/ha/mv-tighten-rounding
waelsy123 Jan 15, 2026
85a27b1
pack storage
godzillaba Jan 15, 2026
e336fbe
Merge branch 'wa/master-vault-isolated' into ha/mv-storage-packing
godzillaba Jan 15, 2026
7de28e9
fmt
godzillaba Jan 15, 2026
3866f91
update comment
godzillaba Jan 15, 2026
33b0506
add error PerformanceFeeUnchanged
godzillaba Jan 15, 2026
0ededc8
improve docs, create constants, add minimum cooldown
godzillaba Jan 15, 2026
9372ad8
Merge pull request #164 from OffchainLabs/ha/mv-storage-packing
waelsy123 Jan 16, 2026
db943e6
... or sublinear previewDeposit
godzillaba Jan 16, 2026
3ac7d1e
Merge pull request #165 from OffchainLabs/ha/mv-fix-setters
waelsy123 Jan 16, 2026
650cc76
test: fix master vault core tests
waelsy123 Jan 20, 2026
592ea0e
set principalPriceWad to 1e18 when vault hold no assets yet
waelsy123 Jan 20, 2026
057cb5f
add virtual asset
waelsy123 Jan 20, 2026
603d26d
fix +1 virtual asset comments
godzillaba Jan 20, 2026
5f365a6
Merge pull request #168 from OffchainLabs/fix/init-principalPriceWad
waelsy123 Jan 20, 2026
339356a
Merge pull request #167 from OffchainLabs/ybb/tests
waelsy123 Jan 20, 2026
e7b8835
tests: add more scenario tests
waelsy123 Jan 22, 2026
6740ad5
Merge pull request #170 from OffchainLabs/ybb/more-tests2
waelsy123 Jan 22, 2026
3eacbce
fix: keeper can be address(0)
waelsy123 Jan 29, 2026
2fb5df2
fix: implement IMasterVault interface
waelsy123 Jan 29, 2026
b8d2e36
fix: approve assets to subvault only during the deposit
waelsy123 Jan 29, 2026
32f2e22
Merge pull request #171 from OffchainLabs/fix/keeper-address-zero
waelsy123 Jan 30, 2026
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
6 changes: 6 additions & 0 deletions contracts/tokenbridge/libraries/vault/IMasterVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

interface IMasterVault {
function setSubVault(address subVault) external;
}
13 changes: 13 additions & 0 deletions contracts/tokenbridge/libraries/vault/IMasterVaultFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: Apache-2.0
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) external;
}
255 changes: 255 additions & 0 deletions contracts/tokenbridge/libraries/vault/MasterVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
// 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 {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract MasterVault is ERC4626, Ownable {
using SafeERC20 for IERC20;
using Math for uint256;

error TooFewSharesReceived();
error TooManySharesBurned();
error TooManyAssetsDeposited();
error TooFewAssetsReceived();
error SubVaultAlreadySet();
error SubVaultCannotBeZeroAddress();
error MustHaveSupplyBeforeSettingSubVault();
error SubVaultAssetMismatch();
error SubVaultExchangeRateTooLow();
error NoExistingSubVault();
error MustHaveSupplyBeforeSwitchingSubVault();
error NewSubVaultExchangeRateTooLow();

// 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;

// 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;

// 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
// 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
uint256 totalPrincipal; // total assets deposited, used to calculate profit

event SubvaultChanged(address indexed oldSubvault, address indexed newSubvault);

constructor(IERC20 _asset, string memory _name, string memory _symbol) ERC20(_name, _symbol) ERC4626(_asset) Ownable() {}

function deposit(uint256 assets, address receiver, uint256 minSharesMinted) public returns (uint256) {
uint256 shares = super.deposit(assets, receiver);
if (shares < minSharesMinted) revert TooFewSharesReceived();
return shares;
}

function withdraw(uint256 assets, address receiver, address _owner, uint256 maxSharesBurned) public returns (uint256) {
uint256 shares = super.withdraw(assets, receiver, _owner);
if (shares > maxSharesBurned) revert TooManySharesBurned();
return shares;
}

function mint(uint256 shares, address receiver, uint256 maxAssetsDeposited) public returns (uint256) {
uint256 assets = super.mint(shares, receiver);
if (assets > maxAssetsDeposited) revert TooManyAssetsDeposited();
return assets;
}

function redeem(uint256 shares, address receiver, address _owner, uint256 minAssetsReceived) public returns (uint256) {
uint256 assets = super.redeem(shares, receiver, _owner);
if (assets < minAssetsReceived) revert TooFewAssetsReceived();
return assets;
}

/// @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(ERC4626 _subVault, uint256 minSubVaultExchRateWad) external onlyOwner {
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 {
_revokeSubVault(minAssetExchRateWad);
}

function _setSubVault(ERC4626 _subVault, uint256 minSubVaultExchRateWad) internal {
if (address(_subVault) == address(0)) revert SubVaultCannotBeZeroAddress();
if (totalSupply() == 0) revert MustHaveSupplyBeforeSettingSubVault();
if (address(_subVault.asset()) != address(asset())) revert SubVaultAssetMismatch();

IERC20(asset()).safeApprove(address(_subVault), type(uint256).max);
uint256 subShares = _subVault.deposit(totalAssets(), address(this));

uint256 _subVaultExchRateWad = subShares.mulDiv(1e18, totalSupply(), Math.Rounding.Down);
if (_subVaultExchRateWad < minSubVaultExchRateWad) revert SubVaultExchangeRateTooLow();
subVaultExchRateWad = _subVaultExchRateWad;

subVault = _subVault;

emit SubvaultChanged(address(0), address(_subVault));
}

function _revokeSubVault(uint256 minAssetExchRateWad) internal {
ERC4626 oldSubVault = subVault;
if (address(oldSubVault) == address(0)) revert NoExistingSubVault();

uint256 _totalSupply = totalSupply();
uint256 assetReceived = oldSubVault.withdraw(oldSubVault.maxWithdraw(address(this)), address(this), address(this));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is an edge case here - the subvault may not have enough liquidity to serve this big withdrawal all at once.

we probably need to make switching vaults more robust to those liquidity constaints.

the same could be said about depositing to the new vault, it could be such a large deposit that slippage starts to become a serious issue

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could do check whether maxWithdraw will return same amount of what master vault actually own

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function withdraw(uint256 assets, address receiver, address owner) public returns (uint256 shares)

note ERC4626.withdraw returns share withdrawn not assetReceived

uint256 effectiveAssetExchRateWad = assetReceived.mulDiv(1e18, _totalSupply, Math.Rounding.Down);
if (effectiveAssetExchRateWad < minAssetExchRateWad) revert TooFewAssetsReceived();

IERC20(asset()).safeApprove(address(oldSubVault), 0);
subVault = ERC4626(address(0));
subVaultExchRateWad = 1e18;

emit SubvaultChanged(address(oldSubVault), address(0));
}

/// @notice Switches to a new subvault or revokes current subvault if newSubVault is zero address
/// @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(ERC4626 newSubVault, uint256 minAssetExchRateWad, uint256 minNewSubVaultExchRateWad) external onlyOwner {
_revokeSubVault(minAssetExchRateWad);

if (address(newSubVault) != address(0)) {
_setSubVault(newSubVault, minNewSubVaultExchRateWad);
}
}

function masterSharesToSubShares(uint256 masterShares, Math.Rounding rounding) public view returns (uint256) {
return masterShares.mulDiv(subVaultExchRateWad, 1e18, rounding);
}

function subSharesToMasterShares(uint256 subShares, Math.Rounding rounding) public view returns (uint256) {
return subShares.mulDiv(1e18, subVaultExchRateWad, rounding);
}

/** @dev See {IERC4626-totalAssets}. */
function totalAssets() public view virtual override returns (uint256) {
ERC4626 _subVault = subVault;
if (address(_subVault) == address(0)) {
return super.totalAssets();
}
return _subVault.convertToAssets(_subVault.balanceOf(address(this)));
}

/** @dev See {IERC4626-maxDeposit}. */
function maxDeposit(address) public view virtual override returns (uint256) {
if (address(subVault) == address(0)) {
return type(uint256).max;
}
return subVault.maxDeposit(address(this));
}

/** @dev See {IERC4626-maxMint}. */
function maxMint(address) public view virtual override returns (uint256) {
uint256 subShares = subVault.maxMint(address(this));
if (subShares == type(uint256).max) {
return type(uint256).max;
}
return subSharesToMasterShares(subShares, Math.Rounding.Down);
}

/**
* @dev Internal conversion function (from assets to shares) with support for rounding direction.
*
* Will revert if assets > 0, totalSupply > 0 and totalAssets = 0. That corresponds to a case where any asset
* would represent an infinite amount of shares.
*/
function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual override returns (uint256 shares) {
ERC4626 _subVault = subVault;
if (address(_subVault) == address(0)) {
return super._convertToShares(assets, rounding);
}
uint256 subShares = rounding == Math.Rounding.Up ? _subVault.previewWithdraw(assets) : _subVault.previewDeposit(assets);
return subSharesToMasterShares(subShares, rounding);
}

/**
* @dev Internal conversion function (from shares to assets) with support for rounding direction.
*/
function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual override returns (uint256 assets) {
ERC4626 _subVault = subVault;
if (address(_subVault) == address(0)) {
return super._convertToAssets(shares, rounding);
}
uint256 subShares = masterSharesToSubShares(shares, rounding);
return rounding == Math.Rounding.Up ? _subVault.previewMint(subShares) : _subVault.previewRedeem(subShares);
}

function totalProfit() public view returns (uint256) {
uint256 _totalAssets = totalAssets();
return _totalAssets > totalPrincipal ? _totalAssets - totalPrincipal : 0;
}

/**
* @dev Deposit/mint common workflow.
*/
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));
}
}

/**
* @dev Withdraw/redeem common workflow.
*/
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 (todo should be a separate beneficiary addr set by 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);
}
}
69 changes: 69 additions & 0 deletions contracts/tokenbridge/libraries/vault/MasterVaultFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: Apache-2.0

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 "./IMasterVault.sol";
import "./IMasterVaultFactory.sol";
import "./MasterVault.sol";

contract MasterVaultFactory is IMasterVaultFactory, OwnableUpgradeable {

error ZeroAddress();

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

function deployVault(address token) public returns (address vault) {
if (token == address(0)) {
revert ZeroAddress();
}

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);

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()));

bytes32 bytecodeHash = keccak256(
abi.encodePacked(
type(MasterVault).creationCode,
abi.encode(token, name, symbol)
)
);
return Create2.computeAddress(bytes32(0), bytecodeHash);
}

function getVault(address token) external returns (address) {
address vault = calculateVaultAddress(token);
if (vault.code.length == 0) {
return deployVault(token);
}
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
) external onlyOwner {
IMasterVault(masterVault).setSubVault(subVault);
emit SubVaultSet(masterVault, subVault);
}
}
18 changes: 18 additions & 0 deletions contracts/tokenbridge/test/MockSubVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract MockSubVault is ERC4626 {
constructor(
IERC20 _asset,
string memory _name,
string memory _symbol
) ERC20(_name, _symbol) ERC4626(_asset) {}

function totalAssets() public view override returns (uint256) {
return IERC20(asset()).balanceOf(address(this));
}
}
Loading
Loading