Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/Vault.sol": {
"content": "// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.24;
import {BaseVault} from "src/BaseVault.sol";
import {FeeMath} from "src/module/FeeMath.sol";
import {VaultLib} from "src/library/VaultLib.sol";
/**
* @title Vault
* @notice Multi-asset vault implementation extending ERC4626 standard
* @dev Provides asset management with fee capabilities and flexible accounting
*
* Key features:
* - Supports multiple deposit assets with common accounting denomination
* - Configurable withdrawal fees managed by fee managers
* - Buffer and strategy allocation for asset management
* - Flexible accounting with real-time or cached asset valuation
* - Role-based access control for administration and fee management
*/
contract Vault is BaseVault {
string public constant VAULT_VERSION = "0.4.0";
bytes32 public constant FEE_MANAGER_ROLE = keccak256("FEE_MANAGER_ROLE");
/**
* @notice Initializes the vault.
* @param admin The address of the admin.
* @param name The name of the vault.
* @param symbol The symbol of the vault.
* @param decimals_ The number of decimals for the vault token.
* @param baseWithdrawalFee_ The base withdrawal fee in basis points (1e8 = 100%).
* @param countNativeAsset_ Whether the vault should count the native asset.
* @param alwaysComputeTotalAssets_ Whether the vault should always compute total assets.
* @param defaultAssetIndex_ The index of the default asset in the asset list.
*/
function initialize(
address admin,
string memory name,
string memory symbol,
uint8 decimals_,
uint64 baseWithdrawalFee_,
bool countNativeAsset_,
bool alwaysComputeTotalAssets_,
uint256 defaultAssetIndex_
) external virtual initializer {
_initialize(
admin, // Address with admin privileges for the vault
name, // Name of the vault token (ERC20)
symbol, // Symbol of the vault token (ERC20)
decimals_, // Decimal precision for the vault token
true, // Start the vault in paused state for safety
countNativeAsset_, // Whether to include native ETH in asset calculations
alwaysComputeTotalAssets_, // Whether to compute assets in real-time vs using cached values
defaultAssetIndex_ // Index of the default asset in the asset list
);
_setBaseWithdrawalFee(baseWithdrawalFee_);
}
//// FEES ////
function _getFeeStorage() internal pure returns (FeeStorage storage) {
return VaultLib.getFeeStorage();
}
/**
* @notice Returns the fee on amount where the fee would get added on top of the amount.
* @param amount The amount on which the fee would get added.
* @param user The address of the user.
* @return The fee amount.
*/
function _feeOnRaw(uint256 amount, address user) public view override returns (uint256) {
return FeeMath.feeOnRaw(amount, _feesToCharge(user));
}
/**
* @notice Returns the fee amount where fee is already included in amount
* @param amount The amount on which the fee is already included.
* @param user The address of the user.
* @return The fee amount.
* @dev Calculates the fee part of an amount `amount` that already includes fees.
* Used in {IERC4626-deposit} and {IERC4626-redeem} operations.
*/
function _feeOnTotal(uint256 amount, address user) public view override returns (uint256) {
return FeeMath.feeOnTotal(amount, _feesToCharge(user));
}
/**
* @notice Returns the fee to charge for a user based on whether the fee is overridden for the user
* @param user The address of the user.
* @return The fee to charge.
*/
function _feesToCharge(address user) internal view returns (uint64) {
FeeStorage storage fees = _getFeeStorage();
bool isFeeOverridenForUser = fees.overriddenBaseWithdrawalFee[user].isOverridden;
if (isFeeOverridenForUser) {
return fees.overriddenBaseWithdrawalFee[user].baseWithdrawalFee;
} else {
return fees.baseWithdrawalFee;
}
}
//// FEES ADMIN ////
/**
* @notice Sets the base withdrawal fee for the vault
* @param baseWithdrawalFee_ The new base withdrawal fee in basis points (1/10000)
* @dev Only callable by accounts with FEE_MANAGER_ROLE
*/
function setBaseWithdrawalFee(uint64 baseWithdrawalFee_) external virtual onlyRole(FEE_MANAGER_ROLE) {
_setBaseWithdrawalFee(baseWithdrawalFee_);
}
/**
* @notice Sets whether the withdrawal fee is exempted for a user
* @param user_ The address of the user
* @param baseWithdrawalFee_ The overridden base withdrawal fee in basis points (1/10000)
* @param toOverride_ Whether to override the withdrawal fee for the user
* @dev Only callable by accounts with FEE_MANAGER_ROLE
*/
function overrideBaseWithdrawalFee(address user_, uint64 baseWithdrawalFee_, bool toOverride_)
external
virtual
onlyRole(FEE_MANAGER_ROLE)
{
_overrideBaseWithdrawalFee(user_, baseWithdrawalFee_, toOverride_);
}
/**
* @notice Internal function to set whether the withdrawal fee is exempted for a user
* @param user_ The address of the user
* @param baseWithdrawalFee_ The overridden base withdrawal fee in basis points (1/10000)
* @param toOverride_ Whether to override the withdrawal fee for the user
*/
function _overrideBaseWithdrawalFee(address user_, uint64 baseWithdrawalFee_, bool toOverride_) internal virtual {
FeeStorage storage fees = _getFeeStorage();
fees.overriddenBaseWithdrawalFee[user_] =
OverriddenBaseWithdrawalFeeFields({baseWithdrawalFee: baseWithdrawalFee_, isOverridden: toOverride_});
emit WithdrawalFeeOverridden(user_, baseWithdrawalFee_, toOverride_);
}
/**
* @dev Internal implementation of setBaseWithdrawalFee
* @param baseWithdrawalFee_ The new base withdrawal fee in basis points (1/10000)
*/
function _setBaseWithdrawalFee(uint64 baseWithdrawalFee_) internal virtual {
if (baseWithdrawalFee_ > FeeMath.BASIS_POINT_SCALE) revert ExceedsMaxBasisPoints(baseWithdrawalFee_);
FeeStorage storage fees = _getFeeStorage();
uint64 oldFee = fees.baseWithdrawalFee;
fees.baseWithdrawalFee = baseWithdrawalFee_;
emit SetBaseWithdrawalFee(oldFee, baseWithdrawalFee_);
}
/**
* @notice Returns the base withdrawal fee
* @return uint64 The base withdrawal fee in basis points (1/10000)
*/
function baseWithdrawalFee() external view returns (uint64) {
return _getFeeStorage().baseWithdrawalFee;
}
/**
* @notice Returns whether the withdrawal fee is exempted for a user
* @param user_ The address of the user
* @return bool Whether the withdrawal fee is exempted for the user
*/
function overriddenBaseWithdrawalFee(address user_)
external
view
returns (OverriddenBaseWithdrawalFeeFields memory)
{
return _getFeeStorage().overriddenBaseWithdrawalFee[user_];
}
}
"
},
"src/BaseVault.sol": {
"content": "// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.24;
import {
AccessControlUpgradeable,
ERC20PermitUpgradeable,
ERC20Upgradeable,
IERC20,
IERC20Metadata,
Math,
ReentrancyGuardUpgradeable,
SafeERC20
} from "src/Common.sol";
import {VaultLib} from "src/library/VaultLib.sol";
import {IVault} from "src/interface/IVault.sol";
import {IStrategy} from "src/interface/IStrategy.sol";
import {FeeMath} from "src/module/FeeMath.sol";
import {IHooks} from "src/interface/IHooks.sol";
import {HooksLib} from "src/library/HooksLib.sol";
import {IERC4626} from "src/Common.sol";
/**
* @title BaseVault
* @notice Base contract for vault implementations that support multiple assets
* @dev This contract implements the ERC4626 standard with extensions to support multiple assets
*
* The BaseVault has two key asset concepts:
*
* 1. Base Asset: The common denomination used internally for accounting.
* - All assets are converted to this base unit for consistent accounting
* - The base asset has a fixed decimal precision (typically 18 decimals)
* - totalBaseAssets() tracks the vault's total value in this base denomination
*
* 2. Default Asset: The underlying asset used for standard ERC4626 operations.
* - Specified by the defaultAssetIndex in VaultStorage
* - Returned by the asset() function
* - Used for deposit(), withdraw(), mint(), and redeem() when no asset is specified
* - Can be the same as base asset (defaultAssetIndex == 0)
* - Can be different from the base asset (defaultAssetIndex == 1)
*
* REQUIREMENT: default Asset MUST be the underyling asset of Base Asset.
* Example: Default Asset is USDC, Base Asset is Wrapped USDC.
*
* The vault maintains a list of supported assets, each with their own decimal precision.
* When assets enter or leave the vault, they are converted to/from the base asset denomination
* for consistent accounting across different asset types.
* The vault also includes a processAccounting function that updates the vault's total assets.
* This function:
* - Computes the current total assets by querying the buffer and other strategies
* - Updates the vault's totalAssets storage value
*
* This accounting mechanism ensures the vault's reported asset values remain accurate over time,
* gas efficiency with accuracy by:
* - Using cached totalAssets values by default to save gas on frequent read operations
* - Providing an explicit processAccounting() function that updates the cached value when needed
* - Offering an alwaysComputeTotalAssets toggle for cases where real-time accuracy is preferred
* despite the higher gas cost of querying external contracts on each totalAssets() call
*/
abstract contract BaseVault is IVault, ERC20PermitUpgradeable, AccessControlUpgradeable, ReentrancyGuardUpgradeable {
using HooksLib for IHooks;
/// INITIALIZATION
/**
* @notice Internal function to initialize the vault.
* @param admin The address of the admin.
* @param name The name of the vault.
* @param symbol The symbol of the vault.
* @param decimals_ The number of decimals for the vault token.
* @param paused_ Whether the vault should start in a paused state.
* @param countNativeAsset_ Whether the vault should count the native asset.
* @param alwaysComputeTotalAssets_ Whether the vault should always compute total assets.
* @param defaultAssetIndex_ The index of the default asset in the asset list.
*/
function _initialize(
address admin,
string memory name,
string memory symbol,
uint8 decimals_,
bool paused_,
bool countNativeAsset_,
bool alwaysComputeTotalAssets_,
uint256 defaultAssetIndex_
) internal virtual {
__ERC20_init(name, symbol);
__AccessControl_init();
__ReentrancyGuard_init();
_grantRole(DEFAULT_ADMIN_ROLE, admin);
VaultStorage storage vaultStorage = _getVaultStorage();
vaultStorage.paused = paused_;
if (decimals_ == 0) {
revert InvalidDecimals();
}
vaultStorage.decimals = decimals_;
vaultStorage.countNativeAsset = countNativeAsset_;
vaultStorage.alwaysComputeTotalAssets = alwaysComputeTotalAssets_;
// The defaultAssetIndex must be 0 or 1 because:
// 1. When an asset is deleted, it's replaced with the last asset in the array
// 2. The base asset (index 0) and default asset should never be deleted
// 3. Therefore, they must be the first two positions in the array
// 4. Or if defaultAssetIndex is 0, then the base asset is also the default asset
if (defaultAssetIndex_ > 1) {
revert InvalidDefaultAssetIndex(defaultAssetIndex_);
}
vaultStorage.defaultAssetIndex = defaultAssetIndex_;
}
/**
* @notice Returns the address of the Default Asset.
* @return address The address of the asset.
* @dev The ERC4626-interface underlying asset is the default asset at defaultAssetIndex
*/
function asset() public view virtual returns (address) {
return _getAssetStorage().list[_getVaultStorage().defaultAssetIndex];
}
/**
* @notice Returns the number of decimals of the vault.
* @return uint256 The number of decimals.
*/
function decimals() public view virtual override(ERC20Upgradeable, IERC20Metadata) returns (uint8) {
return _getVaultStorage().decimals;
}
/**
* @notice Returns the total assets held by the vault denominated in the default asset.
* @dev The ERC4626 interface underyling asset is the default asset.
* @return uint256 The total assets.
*/
function totalAssets() public view virtual returns (uint256) {
return VaultLib.convertBaseToAsset(asset(), totalBaseAssets());
}
/**
* @notice Returns the total assets held by the vault denominated in the Base Asset.
* @dev Either returns the cached total assets or computes them in real-time
* based on the alwaysComputeTotalAssets setting.
* @return uint256 The total base assets.
*/
function totalBaseAssets() public view virtual returns (uint256) {
if (_getVaultStorage().alwaysComputeTotalAssets) {
return computeTotalAssets();
}
return _getVaultStorage().totalAssets;
}
/**
* @notice Returns if the vault is counting native assets.
* @return bool True if the vault is counting native assets.
*/
function countNativeAsset() public view virtual returns (bool) {
return _getVaultStorage().countNativeAsset;
}
/**
* @notice Returns the index of the default asset.
* @return uint256 The index of the default asset.
*/
function defaultAssetIndex() public view virtual returns (uint256) {
return _getVaultStorage().defaultAssetIndex;
}
/**
* @notice Converts a given amount of assets to shares.
* @param assets The amount of assets to convert.
* @return shares The equivalent amount of shares.
*/
function convertToShares(uint256 assets) public view virtual returns (uint256 shares) {
(shares,) = _convertToShares(asset(), assets, Math.Rounding.Floor);
}
/**
* @notice Converts a given amount of shares to assets.
* @param shares The amount of shares to convert.
* @return assets The equivalent amount of assets.
*/
function convertToAssets(uint256 shares) public view virtual returns (uint256 assets) {
(assets,) = _convertToAssets(asset(), shares, Math.Rounding.Floor);
}
/**
* @notice Previews the amount of shares that would be received for a given amount of assets.
* @param assets The amount of assets to deposit.
* @return shares The equivalent amount of shares.
*/
function previewDeposit(uint256 assets) public view virtual returns (uint256 shares) {
(shares,) = _convertToShares(asset(), assets, Math.Rounding.Floor);
}
/**
* @notice Previews the amount of assets that would be required to mint a given amount of shares.
* @param shares The amount of shares to mint.
* @return assets The equivalent amount of assets.
*/
function previewMint(uint256 shares) public view virtual returns (uint256 assets) {
(assets,) = _convertToAssets(asset(), shares, Math.Rounding.Ceil);
}
/**
* @notice Previews the amount of shares that would be required to withdraw a given amount of assets.
* @param assets The amount of assets to withdraw.
* @return shares The equivalent amount of shares.
*/
function previewWithdraw(uint256 assets) public view virtual returns (uint256 shares) {
uint256 fee = _feeOnRaw(assets, _msgSender());
(shares,) = _convertToShares(asset(), assets + fee, Math.Rounding.Ceil);
}
/**
* @notice Previews the amount of assets that would be received for a given amount of shares.
* @param shares The amount of shares to redeem.
* @return assets The equivalent amount of assets.
*/
function previewRedeem(uint256 shares) public view virtual returns (uint256 assets) {
(assets,) = _convertToAssets(asset(), shares, Math.Rounding.Floor);
return assets - _feeOnTotal(assets, _msgSender());
}
/**
* @notice Returns the maximum amount of assets that can be deposited by a given owner.
* @return uint256 The maximum amount of assets.
*/
function maxDeposit(address) public view virtual returns (uint256) {
if (paused()) {
return 0;
}
return type(uint256).max;
}
/**
* @notice Returns the maximum amount of shares that can be minted.
* @return uint256 The maximum amount of shares.
*/
function maxMint(address) public view virtual returns (uint256) {
if (paused()) {
return 0;
}
return type(uint256).max;
}
/**
* @notice Returns the maximum amount of assets that can be withdrawn by a given owner.
* @param owner The address of the owner.
* @return uint256 The maximum amount of assets.
*/
function maxWithdraw(address owner) public view virtual returns (uint256) {
if (paused()) {
return 0;
}
uint256 bufferAssets = IStrategy(buffer()).maxWithdraw(address(this));
if (bufferAssets == 0) {
return 0;
}
uint256 ownerShares = balanceOf(owner);
uint256 maxAssets = previewRedeem(ownerShares);
return bufferAssets < maxAssets ? bufferAssets : maxAssets;
}
/**
* @notice Returns the maximum amount of shares that can be redeemed by a given owner.
* @param owner The address of the owner.
* @return uint256 The maximum amount of shares.
*/
function maxRedeem(address owner) public view virtual returns (uint256) {
if (paused()) {
return 0;
}
uint256 bufferAssets = IStrategy(buffer()).maxWithdraw(address(this));
if (bufferAssets == 0) {
return 0;
}
uint256 ownerShares = balanceOf(owner);
return bufferAssets < previewRedeem(ownerShares) ? previewWithdraw(bufferAssets) : ownerShares;
}
/**
* @notice Deposits a given amount of assets and assigns the equivalent amount of shares to the receiver.
* @param assets The amount of assets to deposit.
* @param receiver The address of the receiver.
* @return uint256 The equivalent amount of shares.
*/
function deposit(uint256 assets, address receiver) public virtual nonReentrant returns (uint256) {
if (paused()) {
revert Paused();
}
return _depositAsset(asset(), assets, receiver);
}
/**
* @notice Mints a given amount of shares and assigns the equivalent amount of assets to the receiver.
* @param shares The amount of shares to mint.
* @param receiver The address of the receiver.
* @return uint256 The equivalent amount of assets.
*/
function mint(uint256 shares, address receiver) public virtual nonReentrant returns (uint256) {
if (paused()) {
revert Paused();
}
(uint256 assets, uint256 baseAssets) = _convertToAssets(asset(), shares, Math.Rounding.Floor);
IHooks hooks_ = hooks();
IHooks.MintParams memory params = IHooks.MintParams({
asset: asset(),
shares: shares,
caller: _msgSender(),
receiver: receiver,
assets: assets,
baseAssets: baseAssets
});
HooksLib.beforeMint(hooks_, params);
_deposit(asset(), _msgSender(), receiver, assets, shares, baseAssets);
HooksLib.afterMint(hooks_, params);
return assets;
}
/**
* @notice Withdraws a given amount of assets and burns the equivalent amount of shares from the owner.
* @param assets The amount of assets to withdraw.
* @param receiver The address of the receiver.
* @param owner The address of the owner.
* @return shares The equivalent amount of shares.
*/
function withdraw(uint256 assets, address receiver, address owner)
public
virtual
nonReentrant
returns (uint256 shares)
{
if (paused()) {
revert Paused();
}
uint256 maxAssets = maxWithdraw(owner);
if (assets > maxAssets) {
revert ExceededMaxWithdraw(owner, assets, maxAssets);
}
shares = previewWithdraw(assets);
IHooks hooks_ = hooks();
IHooks.WithdrawParams memory params = IHooks.WithdrawParams({
asset: asset(),
assets: assets,
caller: _msgSender(),
receiver: receiver,
owner: owner,
shares: shares
});
HooksLib.beforeWithdraw(hooks_, params);
_withdraw(_msgSender(), receiver, owner, assets, shares);
HooksLib.afterWithdraw(hooks_, params);
}
/**
* @notice Redeems a given amount of shares and transfers the equivalent amount of assets to the receiver.
* @param shares The amount of shares to redeem.
* @param receiver The address of the receiver.
* @param owner The address of the owner.
* @return assets The equivalent amount of assets.
*/
function redeem(uint256 shares, address receiver, address owner)
public
virtual
nonReentrant
returns (uint256 assets)
{
if (paused()) {
revert Paused();
}
uint256 maxShares = maxRedeem(owner);
if (shares > maxShares) {
revert ExceededMaxRedeem(owner, shares, maxShares);
}
assets = previewRedeem(shares);
IHooks hooks_ = hooks();
IHooks.RedeemParams memory params = IHooks.RedeemParams({
asset: asset(),
shares: shares,
caller: _msgSender(),
receiver: receiver,
owner: owner,
assets: assets
});
HooksLib.beforeRedeem(hooks_, params);
_withdraw(_msgSender(), receiver, owner, assets, shares);
HooksLib.afterRedeem(hooks_, params);
}
//// 4626-MAX ////
/**
* @notice Returns the list of asset addresses.
* @return addresses The list of asset addresses.
*/
function getAssets() public view virtual returns (address[] memory) {
return _getAssetStorage().list;
}
/**
* @notice Returns the parameters of a given asset.
* @param asset_ The address of the asset.
* @return AssetParams The parameters of the asset.
*/
function getAsset(address asset_) public view virtual returns (AssetParams memory) {
return _getAssetStorage().assets[asset_];
}
/**
* @notice Returns whether a given asset exists in the asset list of the vault.
* @param asset_ The address of the asset.
*/
function hasAsset(address asset_) public view virtual returns (bool) {
AssetStorage storage assetStorage = _getAssetStorage();
AssetParams memory assetParams = assetStorage.assets[asset_];
return assetStorage.list[assetParams.index] == asset_;
}
/**
* @notice Returns the function rule for a given contract address and function signature.
* @param contractAddress The address of the contract.
* @param funcSig The function signature.
* @return FunctionRule The function rule.
*/
function getProcessorRule(address contractAddress, bytes4 funcSig)
public
view
virtual
returns (FunctionRule memory)
{
return _getProcessorStorage().rules[contractAddress][funcSig];
}
/**
* @notice Returns whether the vault is paused.
* @return bool True if the vault is paused, false otherwise.
*/
function paused() public view returns (bool) {
return _getVaultStorage().paused;
}
/**
* @notice Returns the address of the provider.
* @return address The address of the provider.
*/
function provider() public view returns (address) {
return _getVaultStorage().provider;
}
/**
* @notice Returns the address of the buffer strategy.
* @return address The address of the buffer strategy.
*/
function buffer() public view virtual returns (address) {
return _getVaultStorage().buffer;
}
/**
* @notice Previews the amount of shares that would be received for a given amount of assets for a specific asset.
* @param asset_ The address of the asset.
* @param assets The amount of assets to deposit.
* @return shares The equivalent amount of shares.
*/
function previewDepositAsset(address asset_, uint256 assets) public view virtual returns (uint256 shares) {
(shares,) = _convertToShares(asset_, assets, Math.Rounding.Floor);
}
/**
* @notice Deposits a given amount of assets for a specific asset and assigns shares to the receiver.
* @param asset_ The address of the asset.
* @param assets The amount of assets to deposit.
* @param receiver The address of the receiver.
* @return uint256 The equivalent amount of shares.
*/
function depositAsset(address asset_, uint256 assets, address receiver)
public
virtual
nonReentrant
returns (uint256)
{
if (paused()) {
revert Paused();
}
return _depositAsset(asset_, assets, receiver);
}
//// INTERNAL ////
/**
* @notice Internal function to handle deposits for specific assets.
* @param asset_ The address of the asset.
* @param assets The amount of assets to deposit.
* @param receiver The address of the receiver.
* @return uint256 The equivalent amount of shares.
*/
function _depositAsset(address asset_, uint256 assets, address receiver) internal virtual returns (uint256) {
(uint256 shares, uint256 baseAssets) = _convertToShares(asset_, assets, Math.Rounding.Floor);
IHooks hooks_ = hooks();
IHooks.DepositParams memory params = IHooks.DepositParams({
asset: asset_,
assets: assets,
caller: _msgSender(),
receiver: receiver,
shares: shares,
baseAssets: baseAssets
});
HooksLib.beforeDeposit(hooks_, params);
_deposit(asset_, _msgSender(), receiver, assets, shares, baseAssets);
HooksLib.afterDeposit(hooks_, params);
return shares;
}
/**
* @notice Internal function to handle deposits.
* @param asset_ The address of the asset.
* @param caller The address of the caller.
* @param receiver The address of the receiver.
* @param assets The amount of assets to deposit.
* @param shares The amount of shares to mint.
* @param baseAssets The base asset convertion of shares.
*/
function _deposit(
address asset_,
address caller,
address receiver,
uint256 assets,
uint256 shares,
uint256 baseAssets
) internal virtual {
if (!_getAssetStorage().assets[asset_].active) {
revert AssetNotActive();
}
_addTotalAssets(baseAssets);
SafeERC20.safeTransferFrom(IERC20(asset_), caller, address(this), assets);
_mint(receiver, shares);
// 4626 event
emit Deposit(caller, receiver, assets, shares);
// 4626-MAX event
emit DepositAsset(caller, receiver, asset_, assets, baseAssets, shares);
}
/**
* @notice Internal function to add to total assets.
* @param baseAssets The amount of base assets to add.
*/
function _addTotalAssets(uint256 baseAssets) internal virtual {
VaultLib.addTotalAssets(baseAssets);
}
/**
* @notice Internal function to subtract from total assets.
* @param baseAssets The amount of base assets to subtract.
*/
function _subTotalAssets(uint256 baseAssets) internal virtual {
VaultLib.subTotalAssets(baseAssets);
}
/**
* @notice Internal function to handle withdrawals.
* @param caller The address of the caller.
* @param receiver The address of the receiver.
* @param owner The address of the owner.
* @param assets The amount of assets to withdraw.
* @param shares The equivalent amount of shares.
*/
function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares)
internal
virtual
{
VaultStorage storage vaultStorage = _getVaultStorage();
_subTotalAssets(VaultLib.convertAssetToBase(asset(), assets));
if (caller != owner) {
_spendAllowance(owner, caller, shares);
}
// NOTE: burn shares before withdrawing the assets
_burn(owner, shares);
IStrategy(vaultStorage.buffer).withdraw(assets, receiver, address(this));
emit Withdraw(caller, receiver, owner, assets, shares);
}
/**
* @notice Withdraws a given amount of assets and transfers the equivalent amount of shares to the receiver.
* @dev This is a permissioned function that requires the ASSET_WITHDRAWER_ROLE.
* @param asset_ The address of the asset.
* @param assets The amount of assets to withdraw.
* @param receiver The address of the receiver.
* @param owner The address of the owner.
* @return shares The equivalent amount of shares.
*/
function withdrawAsset(address asset_, uint256 assets, address receiver, address owner)
public
virtual
onlyRole(ASSET_WITHDRAWER_ROLE)
returns (uint256 shares)
{
if (paused()) {
revert Paused();
}
(shares,) = _convertToShares(asset_, assets, Math.Rounding.Ceil);
if (assets > IERC20(asset_).balanceOf(address(this)) || balanceOf(owner) < shares) {
revert ExceededMaxWithdraw(owner, assets, IERC20(asset_).balanceOf(address(this)));
}
_withdrawAsset(asset_, _msgSender(), receiver, owner, assets, shares);
}
/**
* @notice Internal function to handle withdrawals for specific assets.
* @param asset_ The address of the asset.
* @param caller The address of the caller.
* @param receiver The address of the receiver.
* @param owner The address of the owner.
* @param assets The amount of assets to withdraw.
* @param shares The equivalent amount of shares.
*/
function _withdrawAsset(
address asset_,
address caller,
address receiver,
address owner,
uint256 assets,
uint256 shares
) internal virtual {
if (!hasAsset(asset_)) revert InvalidAsset(asset_);
_subTotalAssets(_convertAssetToBase(asset_, assets));
if (caller != owner) {
_spendAllowance(owner, caller, shares);
}
// NOTE: burn shares before withdrawing the assets
_burn(owner, shares);
SafeERC20.safeTransfer(IERC20(asset_), receiver, assets);
emit WithdrawAsset(caller, receiver, owner, asset_, assets, shares);
}
/**
* @notice Internal function to convert vault shares to the base asset.
* @param asset_ The address of the asset.
* @param shares The amount of shares to convert.
* @param rounding The rounding direction.
* @return (uint256 assets, uint256 baseAssets) The equivalent amount of assets.
*/
function _convertToAssets(address asset_, uint256 shares, Math.Rounding rounding)
internal
view
virtual
returns (uint256, uint256)
{
return VaultLib.convertToAssets(asset_, shares, rounding);
}
/**
* @notice Internal function to convert assets to shares.
* @param asset_ The address of the asset.
* @param assets The amount of assets to convert.
* @param rounding The rounding direction.
* @return (uint256 shares, uint256 baseAssets) The equivalent amount of shares.
*/
function _convertToShares(address asset_, uint256 assets, Math.Rounding rounding)
internal
view
virtual
returns (uint256, uint256)
{
return VaultLib.convertToShares(asset_, assets, rounding);
}
/**
* @notice Internal function to convert an asset amount to base denomination.
* @param asset_ The address of the asset.
* @param assets The amount of the asset.
* @return uint256 The equivalent amount in base denomination.
*/
function _convertAssetToBase(address asset_, uint256 assets) internal view virtual returns (uint256) {
return VaultLib.convertAssetToBase(asset_, assets);
}
/**
* @notice Internal function to convert base denominated amount to asset value.
* @param asset_ The address of the asset.
* @param assets The amount of the asset.
* @return uint256 The equivalent amount of assets.
*/
function _convertBaseToAsset(address asset_, uint256 assets) internal view virtual returns (uint256) {
return VaultLib.convertBaseToAsset(asset_, assets);
}
/// STORAGE ///
/**
* @notice Internal function to get the vault storage.
* @return The vault storage.
*/
function _getVaultStorage() internal pure virtual returns (VaultStorage storage) {
return VaultLib.getVaultStorage();
}
/**
* @notice Internal function to get the asset storage.
* @return The asset storage.
*/
function _getAssetStorage() internal pure returns (AssetStorage storage) {
return VaultLib.getAssetStorage();
}
/**
* @notice Internal function to get the processor storage.
* @return The processor storage.
*/
function _getProcessorStorage() internal pure returns (ProcessorStorage storage) {
return VaultLib.getProcessorStorage();
}
/**
* @notice Internal function to get the Hooks storage.
* @return $ The Hooks storage.
*/
function _getHooksStorage() internal pure returns (HooksStorage storage) {
return VaultLib.getHooksStorage();
}
//// ADMIN ////
bytes32 public constant PROCESSOR_ROLE = keccak256("PROCESSOR_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant UNPAUSER_ROLE = keccak256("UNPAUSER_ROLE");
bytes32 public constant PROVIDER_MANAGER_ROLE = keccak256("PROVIDER_MANAGER_ROLE");
bytes32 public constant BUFFER_MANAGER_ROLE = keccak256("BUFFER_MANAGER_ROLE");
bytes32 public constant ASSET_MANAGER_ROLE = keccak256("ASSET_MANAGER_ROLE");
bytes32 public constant PROCESSOR_MANAGER_ROLE = keccak256("PROCESSOR_MANAGER_ROLE");
bytes32 public constant HOOKS_MANAGER_ROLE = keccak256("HOOKS_MANAGER_ROLE");
bytes32 public constant ASSET_WITHDRAWER_ROLE = keccak256("ASSET_WITHDRAWER_ROLE");
/**
* @notice Sets the provider.
* @param provider_ The address of the provider.
*/
function setProvider(address provider_) external virtual onlyRole(PROVIDER_MANAGER_ROLE) {
VaultLib.setProvider(provider_);
}
/**
* @notice Sets the buffer strategy.
* @param buffer_ The address of the buffer strategy.
*/
function setBuffer(address buffer_) external virtual onlyRole(BUFFER_MANAGER_ROLE) {
VaultLib.setBuffer(buffer_);
}
/**
* @notice Sets the processor rule for a given contract address and function signature.
* @param target The address of the target contract.
* @param functionSig The function signature.
* @param rule The function rule.
*/
function setProcessorRule(address target, bytes4 functionSig, FunctionRule calldata rule)
public
virtual
onlyRole(PROCESSOR_MANAGER_ROLE)
{
_setProcessorRule(target, functionSig, rule);
}
/**
* @notice Sets the processor rule for a given contract address and function signature.
* @param target The address of the target contract.
* @param functionSig The function signature.
* @param rule The function rule.
*/
function _setProcessorRule(address target, bytes4 functionSig, FunctionRule calldata rule) internal virtual {
_getProcessorStorage().rules[target][functionSig] = rule;
emit SetProcessorRule(target, functionSig, rule);
}
/**
* @notice Sets the processor rule for a given contract address and function signature.
* @param target The address of the target contract.
* @param functionSig The function signature.
* @param rule The function rule.
*/
function setProcessorRules(address[] calldata target, bytes4[] calldata functionSig, FunctionRule[] calldata rule)
public
virtual
onlyRole(PROCESSOR_MANAGER_ROLE)
{
uint256 targetLength = target.length;
if (targetLength != functionSig.length || targetLength != rule.length) {
revert InvalidArray();
}
for (uint256 i = 0; i < targetLength; i++) {
_setProcessorRule(target[i], functionSig[i], rule[i]);
}
}
/**
* @notice Adds a new asset to the vault.
* @param asset_ The address of the asset.
* @param active_ Whether the asset is active or not.
*/
function addAsset(address asset_, bool active_) public virtual onlyRole(ASSET_MANAGER_ROLE) {
_addAsset(asset_, IERC20Metadata(asset_).decimals(), active_);
}
function _addAsset(address asset_, uint8 decimals_, bool active_) internal virtual {
VaultLib.addAsset(asset_, decimals_, active_);
}
/**
* @notice Updates an existing asset's parameters in the vault.
* @param index The index of the asset to update.
* @param fields The AssetUpdateFields struct containing the updated fields.
*/
function updateAsset(uint256 index, AssetUpdateFields calldata fields)
public
virtual
onlyRole(ASSET_MANAGER_ROLE)
{
_updateAsset(index, fields);
}
function _updateAsset(uint256 index, AssetUpdateFields calldata fields) internal virtual {
VaultLib.updateAsset(index, fields);
}
/**
* @notice Deletes an existing asset from the vault.
* @param index The index of the asset to delete.
*/
function deleteAsset(uint256 index) public virtual onlyRole(ASSET_MANAGER_ROLE) {
_deleteAsset(index);
}
function _deleteAsset(uint256 index) internal virtual {
VaultLib.deleteAsset(index);
}
/**
* @notice Sets whether the vault should always compute total assets.
* @param alwaysComputeTotalAssets_ Whether to always compute total assets.
*/
function setAlwaysComputeTotalAssets(bool alwaysComputeTotalAssets_)
external
virtual
onlyRole(ASSET_MANAGER_ROLE)
{
_getVaultStorage().alwaysComputeTotalAssets = alwaysComputeTotalAssets_;
emit SetAlwaysComputeTotalAssets(alwaysComputeTotalAssets_);
if (!alwaysComputeTotalAssets_) {
_processAccounting();
}
}
/**
* @notice Returns whether the vault always computes total assets.
* @return bool True if the vault always computes total assets.
*/
function alwaysComputeTotalAssets() public view virtual returns (bool) {
return _getVaultStorage().alwaysComputeTotalAssets;
}
/**
* @notice Pauses the vault.
*/
function pause() external virtual onlyRole(PAUSER_ROLE) {
if (paused()) {
revert Paused();
}
VaultStorage storage vaultStorage = _getVaultStorage();
vaultStorage.paused = true;
emit Pause(true);
}
/**
* @notice Unpauses the vault.
*/
function unpause() external virtual onlyRole(UNPAUSER_ROLE) {
if (!paused()) {
revert Unpaused();
}
VaultStorage storage vaultStorage = _getVaultStorage();
if (provider() == address(0)) {
revert ProviderNotSet();
}
vaultStorage.paused = false;
emit Pause(false);
}
/**
* @notice Processes the accounting of the vault by calculating the total base balance.
* @dev This function iterates through the list of assets, gets their balances and rates,
* and updates the total assets denominated in the base asset.
*/
function processAccounting() public virtual nonReentrant {
_processAccounting();
}
function _processAccounting() internal virtual {
uint256 totalAssetsBeforeAccounting = totalAssets();
uint256 totalSupplyBeforeAccounting = totalSupply();
uint256 totalBaseAssetsBeforeAccounting = _getVaultStorage().totalAssets;
// handle before hook call
IHooks hooks_ = hooks();
HooksLib.beforeProcessAccounting(
hooks_,
IHooks.BeforeProcessAccountingParams({
totalAssetsBeforeAccounting: totalAssetsBeforeAccounting,
totalSupplyBeforeAccounting: totalSupplyBeforeAccounting,
totalBaseAssetsBeforeAccounting: totalBaseAssetsBeforeAccounting
})
);
/// update total base assets
uint256 totalBaseAssetsAfterAccounting = computeTotalAssets();
_getVaultStorage().totalAssets = totalBaseAssetsAfterAccounting;
// solhint-disable-next-line not-rely-on-time
emit ProcessAccounting(block.timestamp, totalBaseAssetsBeforeAccounting, totalBaseAssetsAfterAccounting);
// handle after hook call
HooksLib.afterProcessAccounting(
hooks_,
IHooks.AfterProcessAccountingParams({
totalAssetsBeforeAccounting: totalAssetsBeforeAccounting,
totalAssetsAfterAccounting: totalAssets(),
totalSupplyBeforeAccounting: totalSupplyBeforeAccounting,
totalSupplyAfterAccounting: totalSupply(),
totalBaseAssetsBeforeAccounting: totalBaseAssetsBeforeAccounting,
totalBaseAssetsAfterAccounting: totalBaseAssetsAfterAccounting
})
);
}
/**
* @notice Computes the total assets in the vault.
* @return totalBaseAssets The total base assets in the vault.
*/
function computeTotalAssets() public view virtual returns (uint256) {
return VaultLib.computeTotalAssets();
}
/**
* @notice Processes a series of calls to target contracts.
* @param targets The addresses of the target contracts.
* @param values The values to send with the calls.
* @param data The calldata for the calls.
* @return returnData The return data from the calls.
*/
function processor(address[] calldata targets, uint256[] memory values, bytes[] calldata data)
external
virtual
onlyRole(PROCESSOR_ROLE)
returns (bytes[] memory returnData)
{
return VaultLib.processor(targets, values, data);
}
/**
* @notice Mints shares to a recipient. Can ONLY be called by the hooks() contract.
* @param recipient The address of the recipient.
* @param shares The amount of shares to mint.
*/
function mintShares(address recipient, uint256 shares) external {
if (msg.sender != address(hooks())) {
revert CallerNotHooks();
}
_mint(recipient, shares);
}
/**
* @notice Sets the hooks contract.
* @param hooks_ The address of the hooks contract.
*/
function setHooks(address hooks_) external onlyRole(HOOKS_MANAGER_ROLE) {
if (hooks_ != address(0) && address(IHooks(hooks_).VAULT()) != address(this)) {
revert InvalidHooks();
}
_setHooks(hooks_);
}
function _setHooks(address hooks_) internal virtual {
HooksStorage storage hooksStorage = _getHooksStorage();
emit SetHooks(address(hooksStorage.hooks), hooks_);
hooksStorage.hooks = IHooks(hooks_);
}
/**
* @notice Returns the hooks contract.
* @return IHooks The hooks contract.
*/
function hooks() public view returns (IHooks) {
return _getHooksStorage().hooks;
}
constructor() {
_disableInitializers();
}
/**
* @notice Fallback function to handle native asset transfers.
*/
receive() external payable {
emit NativeDeposit(msg.value);
}
/// FEES ///
/**
* @notice Returns the fee on amount where the fee would get added on top of the amount.
* @param amount The amount on which the fee would get added.
* @param user The address of the user.
* @return The fee amount.
*/
function _feeOnRaw(uint256 amount, address user) public view virtual override returns (uint256);
/**
* @notice Returns the fee amount where fee is already included in amount
* @param amount The amount on which the fee is already included.
* @param user The address of the user.
* @return The fee amount.
*/
function _feeOnTotal(uint256 amount, address user) public view virtual override returns (uint256);
}
"
},
"src/module/FeeMath.sol": {
"content": "// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.24;
import {Math} from "src/Common.sol";
library FeeMath {
using Math for uint256;
enum FeeType {
OnRaw,
OnTotal
}
error AmountExceedsScale();
error BufferExceedsMax(uint256 bufferAvailable, uint256 bufferMax);
error WithdrawalExceedsBuffer(uint256 withdrawalAmount, uint256 bufferAvailable);
error StartMustBeLessThanEnd(uint256 start, uint256 end);
error UnsupportedFeeType(FeeType feeType);
uint256 public constant BASIS_POINT_SCALE = 1e8;
function linearFee(uint256 amount, uint256 fee, FeeType feeType) internal pure returns (uint256) {
if (feeType == FeeType.OnRaw) {
return feeOnRaw(amount, fee);
} else if (feeType == FeeType.OnTotal) {
return feeOnTotal(amount, fee);
} else {
revert UnsupportedFeeType(feeType);
}
}
function feeOnRaw(uint256 amount, uint256 fee) internal pure returns (uint256) {
return amount.mulDiv(fee, BASIS_POINT_SCALE, Math.Rounding.Ceil);
}
function feeOnTotal(uint256 amount, uint256 fee) internal pure returns (uint256) {
return amount.mulDiv(fee, fee + BASIS_POINT_SCALE, Math.Rounding.Ceil);
}
}
"
},
"src/library/VaultLib.sol": {
"content": "// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.24;
import {IVault} from "src/interface/IVault.sol";
import {IProvider} from "src/interface/IProvider.sol";
import {Math, IERC20} from "src/Common.sol";
import {Guard} from "src/module/Guard.sol";
library VaultLib {
using Math for uint256;
/// @custom:storage-location erc7201:openzeppelin.storage.ERC20
struct ERC20Storage {
mapping(address account => uint256) balances;
mapping(address account => mapping(address spender => uint256)) allowances;
uint256 totalSupply;
string name;
string symbol;
}
/**
* @notice Get the ERC20 storage.
* @return $ The ERC20 storage.
*/
function getERC20Storage() public pure returns (ERC20Storage storage $) {
assembly {
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20")) - 1)) & ~bytes32(uint256(0xff))
$.slot := 0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00
}
}
/**
* @notice Get the vault storage.
* @return $ The vault storage.
*/
function getVaultStorage() public pure returns (IVault.VaultStorage storage $) {
assembly {
// keccak256("yieldnest.storage.vault")
$.slot := 0x22cdba5640455d74cb7564fb236bbbbaf66b93a0cc1bd221f1ee2a6b2d0a2427
}
}
/**
* @notice Get the asset storage.
* @return $ The asset storage.
*/
function getAssetStorage() public pure returns (IVault.AssetStorage storage $) {
assembly {
// keccak256("yieldnest.storage.asset")
$.slot := 0x2dd192a2474c87efcf5ffda906a4b4f8a678b0e41f9245666251cfed8041e680
}
}
/**
* @notice Get the processor storage.
* @return $ The processor storage.
*/
function getProcessorStorage() public pure returns (IVault.ProcessorStorage storage $) {
assembly {
// keccak256("yieldnest.storage.vault")
$.slot := 0x52bb806a772c899365572e319d3d6f49ed2259348d19ab0da8abccd4bd46abb5
}
}
/**
* @notice Get the fee storage.
* @return $ The fee storage.
*/
function getFeeStorage() public pure returns (IVault.FeeStorage storage $) {
assembly {
// keccak256("yieldnest.storage.fees")
$.slot := 0xde924653ae91bd33356774e603163bd5862c93462f31acccae5f965be6e6599b
}
}
/**
* @notice Get the hooks storage.
* @return $ The hooks storage.
*/
function getHooksStorage() public pure returns (IVault.HooksStorage storage $) {
assembly {
// keccak256("yieldnest.storage.hooks")
$.slot := 0x888cd7e3a42ecdcdcee277d5e88e97f4970beef9c0100f5a9297676fe5dfa12f
}
}
/**
* @notice Adds a new asset to the vault.
* @param asset_ The address of the asset.
* @param active_ Whether the asset is active or not.
*/
function addAsset(address asset_, uint8 decimals_, bool active_) public {
if (asset_ == address(0)) {
revert IVault.ZeroAddress();
}
IVault.AssetStorage storage assetStorage = getAssetStorage();
uint256 index = assetStorage.list.length;
IVault.VaultStorage storage vaultStorage = getVaultStorage();
// if native asset is counted the Base Asset should match the decimals count.
if (index == 0 && vaultStorage.countNativeAsset && decimals_ != 18) {
revert IVault.InvalidNativeAssetDecimals(decimals_);
}
// If this is the first asset, check that its decimals are the same as the vault's decimals
if (index == 0 && decimals_ != vaultStorage.decimals) {
revert IVault.InvalidAssetDecimals(decimals_);
}
// If this is not the first asset, check that its decimals are not higher than the base asset
if (index > 0) {
uint8 baseAssetDecimals = assetStorage.assets[assetStorage.list[0]].decimals;
if (decimals_ > baseAssetDecimals) {
revert IVault.InvalidAssetDecimals(decimals_);
}
}
// Check if trying to add the Base Asset again
if (index > 0 && asset_ == assetStorage.list[0]) {
revert IVault.DuplicateAsset(asset_);
}
if (index > 0 && assetStorage.assets[asset_].index != 0) {
revert IVault.DuplicateAsset(asset_);
}
assetStorage.assets[asset_] = IVault.AssetParams({active: active_, index: index, decimals: decimals_});
assetStorage.list.push(asset_);
emit IVault.NewAsset(asset_, decimals_, index);
}
/**
* @notice Updates an existing asset's parameters in the vault.
* @param index The index of the asset to update.
* @param fields The AssetUpdateFields struct containing the updated fields.
*/
function updateAsset(uint256 index, IVault.AssetUpdateFields calldata fields) public {
IVault.AssetStorage storage assetStorage = getAssetStorage();
if (index >= assetStorage.list.length) {
revert IVault.InvalidAsset(address(0));
}
address asset_ = assetStorage.list[index];
IVault.AssetParams storage assetParams = assetStorage.assets[asset_];
assetParams.active = fields.active;
emit IVault.UpdateAsset(index, asset_, fields);
}
/**
* @notice Deletes an existing asset from the vault.
* @param index The index of the asset to delete.
*/
function deleteAsset(uint256 index) public {
IVault.VaultStorage storage vaultStorage = getVaultStorage();
if (index == 0) revert IVault.BaseAsset();
if (index == vaultStorage.defaultAssetIndex) revert IVault.DefaultAsset();
IVault.AssetStorage storage assetStorage = getAssetStorage();
if (index >= assetStorage.list.length) {
revert IVault.InvalidAsset(address(0));
}
address asset_ = assetStorage.list[index];
if (IERC20(asset_).balanceOf(address(this)) > 0) {
revert IVault.AssetNotEmpty(asset_);
}
assetStorage.list[index] = assetStorage.list[assetStorage.list.length - 1];
assetStorage.list.pop();
delete assetStorage.assets[asset_];
// Update the index for the asset that was moved to the deleted position
if (index < assetStorage.list.length) {
address movedAsset = assetStorage.list[index];
assetStorage.assets[movedAsset].index = index;
}
emit IVault.DeleteAsset(index, asset_);
}
/**
* @notice Converts an asset amount to base units.
* @param asset_ The address of the asset.
* @param assets The amount of the asset.
* @return baseAssets The equivalent amount in base units.
*/
function convertAssetToBase(address asset_, uint256 assets) public view returns (uint256 baseAssets) {
if (asset_ == address(0)) revert IVault.ZeroAddress();
uint256 rate = IProvider(getVaultStorage().provider).getRate(asset_);
baseAssets = assets.mulDiv(rate, 10 ** (getAssetStorage().assets[asset_].decimals), Math.Rounding.Floor);
}
/**
* @notice Converts a base amount to asset units.
* @param asset_ The address of the asset.
* @param baseAssets The amount of the assets in base units.
* @return assets The equivalent amount in asset units.
*/
function convertBaseToAsset(address asset_, uint256 baseAssets) public view returns (uint256 assets) {
if (asset_ == address(0)) revert IVault.ZeroAddress();
uint256 rate = IProvider(getVaultStorage().provider).getRate(asset_);
assets = baseAssets.mulDiv(10 ** (getAssetStorage().assets[asset_].decimals), rate, Math.Rounding.Floor);
}
/**
* @notice Adds a given amount of base assets to the total assets.
* @param baseAssets The amount of base assets to add.
*/
function addTotalAssets(uint256 baseAssets) public {
IVault.VaultStorage storage vaultStorage = getVaultStorage();
if (!vaultStorage.alwaysComputeTotalAssets) {
vaultStorage.totalAssets += baseAssets;
}
}
/**
* @notice Subtracts a given amount of base assets from the total assets.
* @param baseAssets The amount of base assets to subtract.
*/
function subTotalAssets(uint256 baseAssets) public {
IVault.VaultStorage storage vaultStorage = getVaultStorage();
if (!vaultStorage.alwaysComputeTotalAssets) {
vaultStorage.totalAssets -= baseAssets;
}
}
/**
* @notice Converts a given amount of shares to assets.
* @param asset_ The address of the asset.
* @param shares The amount of shares to convert.
* @param rounding The rounding direction.
* @return assets The amount of assets.
* @return baseAssets The amount of base assets.
*/
function convertToAssets(address asset_, uint256 shares, Math.Rounding rounding)
public
view
returns (uint256 assets, uint256 baseAssets)
{
uint256 totalAssets = IVault(address(this)).totalBaseAssets();
uint256 totalSupply = getERC20Storage().totalSupply;
baseAssets = shares.mulDiv(totalAssets + 1, totalSupply + 1, rounding);
assets = convertBaseToAsset(asset_, baseAssets);
}
/**
* @notice Converts a given amount of assets to shares.
* @param asset_ The address of the asset.
* @param assets The amount of assets to convert.
* @param rounding The rounding direction.
* @return (shares, baseAssets) The equivalent amount of shares.
*/
function convertToShares(address asset_, uint256 assets, Math.Rounding rounding)
public
view
returns (uint256, uint256)
{
uint256 totalAssets = IVault(address(this)).totalBaseAssets();
uint256 totalSupply = getERC20Storage().totalSupply;
uint256 baseAssets = convertAssetToBase(asset_, assets);
uint256 shares = baseAssets.mulDiv(totalSupply + 1, totalAssets + 1, rounding);
return (shares, baseAssets);
}
/**
* @notice Sets the processor rule for a given contract address and function signature.
* @param target The address of the target contract.
* @param functionSig The function signature.
* @param rule The function rule.
*/
function setProcessorRule(address target, bytes4 functionSig, IVault.FunctionRule calldata rule) public {
getProcessorStorage().rules[target][functionSig] = rule;
emit IVault.SetProcessorRule(target, functionSig, rule);
}
/**
* @notice Sets the provider.
* @param provider_ The address of the provider.
*/
function setProvider(address provider_) public {
if (provider_ == address(0)) {
revert IVault.ZeroAddress();
}
getVaultStorage().provider = provider_;
emit IVault.SetProvider(provider_);
}
/**
* @notice Sets the buffer strategy.
* @param buffer_ The address of the buffer strategy.
*/
function setBuffer(address buffer_) public {
if (buffer_ == address(0)) {
revert IVault.ZeroAddress();
}
getVaultStorage().buffer = buffer_;
emit IVault.SetBuffer(buffer_);
}
/**
* @notice Computes the total assets in the vault.
* @return totalBaseBalance The total base balance of the vault.
*/
function computeTotalAssets() public view returns (uint256 totalBaseBalance) {
IVault.VaultStorage storage vaultStorage = getVaultStorage();
// Assumes native asset has same decimals as asset() (the base asset)
totalBaseBalance = vaultStorage.countNativeAsset ? address(this).balance : 0;
IVault.AssetStorage storage assetStorage = getAssetStorage();
address[] memory assetList = assetStorage.list;
uint256 assetListLength = assetList.length;
for (uint256 i = 0; i < assetListLength; i++) {
uint256 balance = IERC20(assetList[i]).balanceOf(address(this));
if (balance == 0) continue;
totalBaseBalance += convertAssetToBase(assetList[i], balance);
}
}
/**
* @notice Processes a series of calls to target contracts.
* @param targets The addresses of the target contracts.
* @param values The values to send with the calls.
* @param data The calldata for the calls.
* @return returnData The return data from the calls.
*/
function processor(address[] calldata targets, uint256[] memory values, bytes[] calldata data)
public
returns (bytes[] memory returnData)
{
uint256 targetsLength = targets.length;
returnData = new bytes[](targetsLength);
for (uint256 i = 0; i < targetsLength; i++) {
Guard.validateCall(targets[i], values[i], data[i]);
(bool success, bytes memory returnData_) = targets[i].call{value: values[i]}(data[i]);
if (!success) {
revert IVault.ProcessFailed(data[i], returnData_);
}
returnData[i] = returnData_;
}
emit IVault.ProcessSuccess(targets, values, returnData);
}
}
"
},
"src/Common.sol": {
"content": "/* solhint-disable no-empty-blocks, no-unused-import */
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.24;
import {AccessControlUpgradeable} from
"lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol";
import {Address} from "lib/openzeppelin-contracts/contracts/utils/Address.sol";
import {ERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import {ERC20PermitUpgradeable} from
"lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
import {ERC20Upgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol";
import {IAccessControl} from "lib/openzeppelin-contracts/contracts/access/IAccessControl.sol";
import {IERC20} from "lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol";
import {IERC20Metadata} from "lib/openzeppelin-contracts/contracts/interfaces/IERC20Metadata.sol";
import {IERC20Permit} from "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol";
import {IERC4626} from "lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol";
import {Math} from "lib/openzeppelin-contracts/contracts/utils/math/Math.sol";
import {ProxyAdmin} from "lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol";
import {ReentrancyGuardUpgradeable} from
"lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol";
import {SafeERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {TimelockController} from "lib/openzeppelin-contracts/contracts/governance/TimelockController.sol";
import {TransparentUpgradeableProxy} from
"lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {IERC165} from "lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol";
import {Initializable} from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
import {OwnableUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol";
import {Ownable} from "lib/openzeppelin-contracts/contracts/access/Ownable.sol";
contract Common {}
"
},
"src/interface/IVault.sol": {
"content": "// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.24;
import {IERC4626} from "src/Common.sol";
import {IValidator} from "src/interface/IValidator.sol";
import {IHooks} from "src/interface/IHooks.sol";
interface IVault is IERC4626 {
struct VaultStorage {
uint256 totalAssets;
address provider;
address buffer;
bool paused;
uint8 decimals;
bool countNativeAsset;
bool alwaysComputeTotalAssets;
/// @notice The index of the default asset.
/// The default asset is vault.asset(), used for deposit, withdraw, redeem, mint as default.
/// If defaultAssetIndex is 0, the vault will use the base asset as default asset.
uint256 defaultAssetIndex;
}
struct AssetParams {
uint256 index;
bool active;
uint8 decimals;
}
struct AssetUpdateFields {
bool active;
}
struct AssetStorage {
mapping(address => AssetParams) assets;
address[] list;
}
struct OverriddenBaseWithdrawalFeeFields {
/// @
Submitted on: 2025-09-24 19:59:39
Comments
Log in to comment.
No comments yet.