Vault

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 {
        /// @

Tags:
ERC20, ERC165, Multisig, Mintable, Pausable, Yield, Voting, Timelock, Upgradeable, Multi-Signature, Factory|addr:0x9c1713bc42dcf621038f4016664ffab096a05410|verified:true|block:23433014|tx:0xbcf7c9469cea6bb64df7f09f5342175c550412c4eeb90183d8dd5eadb1acf031|first_check:1758736778

Submitted on: 2025-09-24 19:59:39

Comments

Log in to comment.

No comments yet.