MultisigStrategy

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/periphery/strategies/MultisigStrategy.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import {BaseStrategy} from "./BaseStrategy.sol";
import {BaseStrategyStorageLib as BaseStrategyStorage} from "../lib/BaseStrategyStorageLib.sol";
import {MultisigStrategyStorageLib as MultisigStrategyStorage} from "../lib/MultisigStrategyStorageLib.sol";
import {PositionAccountingStorageLib} from "../lib/PositionAccountingStorageLib.sol";
import {PositionAccountingLib} from "../lib/PositionAccountingLib.sol";
import {PeripheryRolesLib} from "../lib/PeripheryRolesLib.sol";
import {StrategyType} from "../../interface/IStrategyTemplate.sol";
import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";
import {IERC4626} from "@openzeppelin-contracts/interfaces/IERC4626.sol";
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@openzeppelin-contracts/utils/math/Math.sol";
import {SafeCast} from "@openzeppelin-contracts/utils/math/SafeCast.sol";
import {SignedMath} from "@openzeppelin-contracts/utils/math/SignedMath.sol";

/**
 * @title MultisigStrategy
 * @dev A strategy that forwards assets to a designated multi-signature wallet
 * @dev Implements BaseStrategy for integration with the vault system
 * @dev This strategy simply forwards deposits to a multi-sig wallet and retrieves
 *      them on withdrawal. It does not generate any rewards.
 * @dev Uses EIP-7201 storage layout for upgradeability.
 */
contract MultisigStrategy is BaseStrategy {
    using SafeERC20 for IERC20;
    using Math for uint256;
    using SafeCast for int256;
    using SignedMath for int256;

    /// @notice Emitted when the multi-sig is set
    /// @param multiSig The address of the multi-sig wallet
    /// @param newMultiSig The new address of the multi-sig wallet
    event MultiSigSet(address indexed multiSig, address indexed newMultiSig);

    /// @notice Emitted when assets are forwarded to the multi-sig
    /// @param multiSig The address of the multi-sig wallet
    /// @param asset The address of the asset forwarded
    /// @param amount The amount of assets forwarded
    event AssetsForwarded(address indexed multiSig, address asset, uint256 amount);

    /// @notice Emitted when assets are retrieved from the multi-sig
    /// @param multiSig The address of the multi-sig wallet
    /// @param asset The address of the asset retrieved
    /// @param amount The amount of assets retrieved
    event AssetsRetrieved(address indexed multiSig, address asset, uint256 amount);

    /// @notice Emitted when the total assets are adjusted
    /// @param accountingNonce The accounting nonce
    /// @param totalAssets The new total assets
    /// @param diff The amount of underlying assets to adjust the total assets by
    event AdjustTotalAssets(uint256 accountingNonce, uint256 totalAssets, int256 diff);

    /// @notice Custom errors
    error InvalidMultiSigAddress();
    error InsufficientUnderlyingBalance();
    error NotAdminOrOperator();

    /// @notice Modifier to ensure only admin or operator can call functions
    modifier onlyAdminOrOperator() {
        if (
            !hasRole(PeripheryRolesLib.STRATEGY_ADMIN, msg.sender)
                && !hasRole(PeripheryRolesLib.OPERATOR_ROLE, msg.sender)
        ) {
            revert NotAdminOrOperator();
        }
        _;
    }

    /**
     * @dev Constructor that disables initializers
     */
    constructor() {
        _disableInitializers();
    }

    /**
     * @dev Initializer function
     * @param admin The address that will have admin role
     * @param vault_ The address of the authorized vault
     * @param multiSig_ The address of the multi-signature wallet
     * @param maxAccountingChangeThreshold_ The maximum accounting change threshold in basis points
     * @param accountingValidityPeriod_ The accounting validity period in seconds
     * @param cooldownPeriod_ The update cooldown period in seconds
     */
    function initialize(
        address admin,
        address vault_,
        address multiSig_,
        uint64 maxAccountingChangeThreshold_,
        uint64 accountingValidityPeriod_,
        uint64 cooldownPeriod_
    ) external initializer {
        if (multiSig_ == address(0)) revert InvalidMultiSigAddress();
        if (cooldownPeriod_ >= accountingValidityPeriod_) {
            revert PositionAccountingLib.InvalidCooldownPeriod();
        }
        if (maxAccountingChangeThreshold_ > PositionAccountingLib.BASIS_POINTS) {
            revert PositionAccountingLib.InvalidMaxAccountingChangeThreshold();
        }

        // Initialize BaseStrategy first
        _initializeBaseStrategy(admin, vault_);

        // Initialize MultisigStrategyStorage
        MultisigStrategyStorage.initialize(multiSig_);

        // Initialize PositionAccountingStorage
        PositionAccountingStorageLib.initialize(
            cooldownPeriod_, maxAccountingChangeThreshold_, accountingValidityPeriod_
        );
    }

    /**
     * GETTER FUNCTIONS
     */

    /**
     * @notice Returns the address of the multi-signature wallet
     * @return The address of the multi-signature wallet
     */
    function getMultiSig() external view returns (address) {
        MultisigStrategyStorage.MultisigStrategyStorage storage multisigStrategyStorage =
            MultisigStrategyStorage.fetch();
        return multisigStrategyStorage.multiSig;
    }

    /**
     * @notice Returns the next accounting nonce
     * @return The next accounting nonce
     */
    function getNextAccountingNonce() public view returns (uint256) {
        return PositionAccountingLib.getNextAccountingNonce();
    }

    /**
     * @notice Returns whether an address has the operator role
     * @param account The address to check
     * @return True if the address has the operator role
     */
    function isOperator(address account) external view returns (bool) {
        return hasRole(PeripheryRolesLib.OPERATOR_ROLE, account);
    }

    /**
     * @notice Returns the last updated timestamp
     * @return The last updated timestamp
     */
    function getLastUpdatedTimestamp() external view returns (uint64) {
        return PositionAccountingLib.getLastUpdatedTimestamp();
    }

    /**
     * @notice Returns the max accounting change threshold
     * @return The max accounting change threshold
     */
    function getMaxAccountingChangeThreshold() external view returns (uint64) {
        return PositionAccountingLib.getMaxAccountingChangeThreshold();
    }

    /**
     * @notice Returns the accounting validity period
     * @return The accounting validity period
     */
    function getAccountingValidityPeriod() external view returns (uint64) {
        return PositionAccountingLib.getAccountingValidityPeriod();
    }

    /**
     * @notice Returns the update cooldown period
     * @return The update cooldown period
     */
    function getCooldownPeriod() external view returns (uint64) {
        return PositionAccountingLib.getCooldownPeriod();
    }

    /**
     * @dev Override strategyType for MultisigStrategy
     * @dev MultisigStrategy is an async strategy that requires multisig approval for operations
     * @return ASYNC strategy type
     */
    function strategyType() external pure override returns (StrategyType) {
        return StrategyType.ASYNC;
    }

    /**
     * SETTER FUNCTIONS
     */

    /**
     * @notice Sets the address of the multi-signature wallet
     * @param multiSig_ The address of the multi-signature wallet
     * @dev The multiSig must approve this contract to pull funds for withdrawals
     */
    function setMultiSig(address multiSig_) external onlyRole(PeripheryRolesLib.STRATEGY_ADMIN) {
        if (multiSig_ == address(0)) revert InvalidMultiSigAddress();
        MultisigStrategyStorage.MultisigStrategyStorage storage multisigStrategyStorage =
            MultisigStrategyStorage.fetch();
        emit MultiSigSet(multisigStrategyStorage.multiSig, multiSig_);
        multisigStrategyStorage.multiSig = multiSig_;
    }

    /**
     * @notice Sets the max accounting change threshold
     * @param maxAccountingChangeThreshold_ The maximum accounting change threshold in basis points (10000 = 100%, 100 = 1%)
     */
    function setMaxAccountingChangeThreshold(uint64 maxAccountingChangeThreshold_)
        external
        onlyRole(PeripheryRolesLib.STRATEGY_ADMIN)
    {
        PositionAccountingLib.setMaxAccountingChangeThreshold(maxAccountingChangeThreshold_);
    }

    /**
     * @notice Sets the accounting change validation period
     * @param accountingValidityPeriod_ The new accounting change validation period in seconds
     */
    function setAccountingValidityPeriod(uint64 accountingValidityPeriod_)
        external
        onlyRole(PeripheryRolesLib.STRATEGY_ADMIN)
    {
        PositionAccountingLib.setAccountingValidityPeriod(accountingValidityPeriod_);
    }

    /**
     * @notice Sets the accounting update cooldown period
     * @param cooldownPeriod_ The new update cooldown period in seconds
     */
    function setCooldownPeriod(uint64 cooldownPeriod_) external onlyRole(PeripheryRolesLib.STRATEGY_ADMIN) {
        PositionAccountingLib.setCooldownPeriod(cooldownPeriod_);
    }

    /**
     * ADMIN STATE MODIFYING FUNCTIONS
     */

    /**
     * @notice Unpauses the strategy and adjusts the total assets
     * @dev Can only be called by the owner
     * @dev Skips the validation of the accounting change, used by the admin multisig to fix the accounting when the strategy is paused
     * @dev Allows the admin to correct the accounting when the strategy is paused in a single transaction even if the change exceeds the max accounting change threshold
     * @param diff The amount of underlying assets to adjust the total assets by
     */
    function unpauseAndAdjustTotalAssets(int256 diff) external onlyRole(PeripheryRolesLib.STRATEGY_ADMIN) {
        _unpause();
        _adjustTotalAssets(diff);
    }

    /**
     * @notice Handles the accounting for the amount of underlying assets owned by the multisig wallet and by its positions.
     * @param diff The amount of underlying assets to adjust the total assets by
     */
    function adjustTotalAssets(int256 diff, uint256 accountingNonce_) external whenNotPaused onlyAdminOrOperator {
        MultisigStrategyStorage.MultisigStrategyStorage storage multisigStrategyStorage =
            MultisigStrategyStorage.fetch();

        // Validate the accounting change
        if (
            !PositionAccountingLib.isValidAccountingChange(
                diff, accountingNonce_, multisigStrategyStorage.vaultDepositedAmount
            )
        ) {
            _pause();
            return;
        }

        // Adjust the total assets
        _adjustTotalAssets(diff);
    }

    /**
     * INTERNAL FUNCTIONS
     */

    /**
     * @dev function to preview the current position value
     * @return The current vault deposited amount
     */
    function _previewPosition() internal view override returns (uint256) {
        // Check accounting validity period
        PositionAccountingLib._checkAccountingValidity();

        MultisigStrategyStorage.MultisigStrategyStorage storage multisigStrategyStorage =
            MultisigStrategyStorage.fetch();
        return multisigStrategyStorage.vaultDepositedAmount;
    }

    /**
     * @dev function to allocate funds to the position
     * @param data The data containing the amount to allocate
     * @return The actual amount allocated
     */
    function _allocateToPosition(bytes calldata data) internal override whenNotPaused returns (uint256) {
        uint256 amount;
        assembly {
            amount := calldataload(data.offset)
        }

        PositionAccountingLib._checkAccountingValidity();
        MultisigStrategyStorage.MultisigStrategyStorage storage multisigStrategyStorage =
            MultisigStrategyStorage.fetch();
        multisigStrategyStorage.vaultDepositedAmount += amount;

        BaseStrategyStorage.BaseStrategyStorage storage baseStrategyStorage = BaseStrategyStorage.fetch();
        IERC20(baseStrategyStorage.asset).safeTransfer(multisigStrategyStorage.multiSig, amount);
        emit AssetsForwarded(multisigStrategyStorage.multiSig, baseStrategyStorage.asset, amount);

        return amount;
    }

    /**
     * @dev Internal function to retrieve assets from the multisig
     * @param amount The amount of assets to retrieve
     * @return The actual amount retrieved
     */
    function _retrieveAssetsFromMultisig(uint256 amount) internal whenNotPaused returns (uint256) {
        PositionAccountingLib._checkAccountingValidity();

        MultisigStrategyStorage.MultisigStrategyStorage storage multisigStrategyStorage =
            MultisigStrategyStorage.fetch();

        if (amount > multisigStrategyStorage.vaultDepositedAmount) revert InsufficientUnderlyingBalance();
        multisigStrategyStorage.vaultDepositedAmount -= amount;

        BaseStrategyStorage.BaseStrategyStorage storage baseStrategyStorage = BaseStrategyStorage.fetch();
        IERC20(baseStrategyStorage.asset).safeTransferFrom(multisigStrategyStorage.multiSig, address(this), amount);
        emit AssetsRetrieved(multisigStrategyStorage.multiSig, baseStrategyStorage.asset, amount);

        return amount;
    }

    /**
     * @dev  function to deallocate funds from the position
     * @param data The data containing the amount to deallocate
     * @return The actual amount deallocated
     */
    function _deallocateFromPosition(bytes calldata data) internal override returns (uint256) {
        uint256 amount;
        assembly {
            amount := calldataload(data.offset)
        }
        return _retrieveAssetsFromMultisig(amount);
    }

    /**
     * @dev function to withdraw funds from the position
     * @param assets The amount of assets to withdraw
     * @return The actual amount withdrawn
     */
    function _withdrawFromPosition(uint256 assets) internal override returns (uint256) {
        return _retrieveAssetsFromMultisig(assets);
    }

    /**
     * @notice Internal function to adjust total assets
     * @param diff The amount of underlying assets to adjust the total assets by
     */
    function _adjustTotalAssets(int256 diff) internal {
        MultisigStrategyStorage.MultisigStrategyStorage storage multisigStrategyStorage =
            MultisigStrategyStorage.fetch();

        // Update timestamp and nonce
        uint256 newNonce = PositionAccountingLib.updateTimestampAndNonce();

        if (diff < 0) {
            uint256 absDiff = uint256(-diff);
            if (absDiff > multisigStrategyStorage.vaultDepositedAmount) revert InsufficientUnderlyingBalance();
            multisigStrategyStorage.vaultDepositedAmount -= absDiff;
        } else {
            multisigStrategyStorage.vaultDepositedAmount += uint256(diff);
        }
        emit AdjustTotalAssets(newNonce, multisigStrategyStorage.vaultDepositedAmount, diff);
    }
}
"
    },
    "src/periphery/strategies/BaseStrategy.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import {IBaseStrategy} from "../interface/IBaseStrategy.sol";
import {IStrategyTemplate, StrategyType} from "../../interface/IStrategyTemplate.sol";
import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";
import {IERC4626} from "@openzeppelin-contracts/interfaces/IERC4626.sol";
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {AccessControlUpgradeable} from "@openzeppelin-upgradeable/access/AccessControlUpgradeable.sol";
import {PausableUpgradeable} from "@openzeppelin-upgradeable/utils/PausableUpgradeable.sol";
import {Initializable} from "@openzeppelin-upgradeable/proxy/utils/Initializable.sol";
import {BaseStrategyStorageLib as BaseStrategyStorage} from "../lib/BaseStrategyStorageLib.sol";
import {PeripheryRolesLib} from "../lib/PeripheryRolesLib.sol";

/**
 * @title BaseStrategy
 * @dev Abstract base strategy implementation that provides common functionality
 * @dev for all strategy implementations including yield accrual and position management.
 * @dev Uses EIP-7201 storage layout for upgradeability.
 */
abstract contract BaseStrategy is IBaseStrategy, AccessControlUpgradeable, PausableUpgradeable {
    using SafeERC20 for IERC20;

    /**
     * @dev Constructor that disables initializers
     */
    constructor() {
        _disableInitializers();
    }

    /**
     * @dev External initializer function
     * @param admin The address that will have admin role
     * @param vault_ The address of the authorized vault
     */
    function initialize(address admin, address vault_) external initializer {
        _initializeBaseStrategy(admin, vault_);
    }

    /**
     * @dev Internal initializer function that can be called by child contracts
     * @param admin The address that will have admin role
     * @param vault_ The address of the authorized vault
     */
    function _initializeBaseStrategy(address admin, address vault_) internal virtual {
        if (admin == address(0)) revert ZeroAdminAddress();
        if (vault_ == address(0)) revert ZeroVaultAddress();

        __AccessControl_init();
        __Pausable_init();
        _setRoleAdmin(PeripheryRolesLib.STRATEGY_ADMIN, PeripheryRolesLib.STRATEGY_ADMIN_ADMIN);
        _setRoleAdmin(PeripheryRolesLib.OPERATOR_ROLE, PeripheryRolesLib.OPERATOR_ADMIN);
        _grantRole(PeripheryRolesLib.STRATEGY_ADMIN_ADMIN, admin);
        _grantRole(PeripheryRolesLib.OPERATOR_ADMIN, admin);
        _grantRole(PeripheryRolesLib.OPERATOR_ROLE, admin);
        _grantRole(PeripheryRolesLib.STRATEGY_ADMIN, admin);

        BaseStrategyStorage.BaseStrategyStorage storage baseStrategyStorage = BaseStrategyStorage.fetch();
        baseStrategyStorage.vault = vault_;
        baseStrategyStorage.asset = IERC4626(vault_).asset();
        // Default to unlimited withdrawals
        baseStrategyStorage.maxWithdraw = type(uint256).max;
    }

    /**
     * @dev Modifier to ensure only the authorized vault can call functions
     */
    modifier onlyVault() {
        BaseStrategyStorage.BaseStrategyStorage storage baseStrategyStorage = BaseStrategyStorage.fetch();
        if (msg.sender != baseStrategyStorage.vault) revert UnauthorizedVault();
        _;
    }

    /**
     * @dev Abstract virtual function to preview the current position value
     * @dev Must be implemented by derived strategies to return the current position value
     * @return The current position value
     */
    function _previewPosition() internal view virtual returns (uint256);

    /**
     * @dev Abstract virtual function to allocate funds to the position
     * @param data The data containing the amount to allocate and protocol specific data
     * @return The actual amount allocated
     */
    function _allocateToPosition(bytes calldata data) internal virtual returns (uint256);

    /**
     * @dev Abstract virtual function to deallocate funds from the position
     * @param data The data containing the amount to deallocate and protocol specific data
     * @return The actual amount deallocated
     */
    function _deallocateFromPosition(bytes calldata data) internal virtual returns (uint256);

    /**
     * @dev Abstract virtual function to withdraw funds from the position
     * @param assets The amount of assets to withdraw
     * @return The actual amount withdrawn
     */
    function _withdrawFromPosition(uint256 assets) internal virtual returns (uint256);

    /**
     * @inheritdoc IStrategyTemplate
     */
    function allocateFunds(bytes calldata data) external virtual override onlyVault whenNotPaused returns (uint256) {
        uint256 amountToAllocate;
        assembly {
            amountToAllocate := calldataload(data.offset)
        }
        if (amountToAllocate == 0) return 0;

        // Cache storage reference to avoid multiple sloads
        BaseStrategyStorage.BaseStrategyStorage storage baseStrategyStorage = BaseStrategyStorage.fetch();
        address vault = baseStrategyStorage.vault;

        IERC20(baseStrategyStorage.asset).safeTransferFrom(vault, address(this), amountToAllocate);

        uint256 actualAllocated = _allocateToPosition(data);

        emit AllocateFunds(actualAllocated);
        return actualAllocated;
    }

    /**
     * @inheritdoc IStrategyTemplate
     */
    function deallocateFunds(bytes calldata data) external virtual override onlyVault whenNotPaused returns (uint256) {
        uint256 amountToDeallocate;
        assembly {
            amountToDeallocate := calldataload(data.offset)
        }

        if (amountToDeallocate == 0) return 0;

        uint256 actualDeallocated = _deallocateFromPosition(data);

        BaseStrategyStorage.BaseStrategyStorage storage baseStrategyStorage = BaseStrategyStorage.fetch();
        IERC20(baseStrategyStorage.asset).safeTransfer(baseStrategyStorage.vault, actualDeallocated);

        emit DeallocateFunds(actualDeallocated);
        return actualDeallocated;
    }

    /**
     * @inheritdoc IStrategyTemplate
     */
    function onWithdraw(uint256 assets) external virtual override onlyVault whenNotPaused returns (uint256) {
        // Check maxWithdraw limit
        uint256 maxWithdrawAmount = this.maxWithdraw();
        if (assets > maxWithdrawAmount) revert MaxWithdrawAmountExceeded();

        uint256 actualWithdrawn = _withdrawFromPosition(assets);

        BaseStrategyStorage.BaseStrategyStorage storage baseStrategyStorage = BaseStrategyStorage.fetch();
        IERC20(baseStrategyStorage.asset).safeTransfer(baseStrategyStorage.vault, actualWithdrawn);

        emit StrategyWithdraw(actualWithdrawn);
        return actualWithdrawn;
    }

    /**
     * @inheritdoc IStrategyTemplate
     */
    function asset() external view virtual override returns (address) {
        return BaseStrategyStorage.fetch().asset;
    }

    /**
     * @inheritdoc IStrategyTemplate
     */
    function totalAllocatedValue() external view virtual override whenNotPaused returns (uint256) {
        return _previewPosition();
    }

    /**
     * @inheritdoc IStrategyTemplate
     */
    function maxAllocation() external pure virtual override returns (uint256) {
        return type(uint256).max;
    }

    /**
     * @inheritdoc IStrategyTemplate
     */
    function maxWithdraw() external view virtual override returns (uint256) {
        BaseStrategyStorage.BaseStrategyStorage storage baseStrategyStorage = BaseStrategyStorage.fetch();
        uint256 positionValue = _previewPosition();
        return baseStrategyStorage.maxWithdraw > positionValue ? positionValue : baseStrategyStorage.maxWithdraw;
    }

    /**
     * @dev Get the authorized vault address
     * @return The authorized vault address
     */
    function getVault() external view virtual returns (address) {
        return BaseStrategyStorage.fetch().vault;
    }

    /**
     * @dev Set the maximum withdraw amount for the strategy
     * @param maxWithdraw_ The maximum amount that can be withdrawn
     */
    function setMaxWithdraw(uint256 maxWithdraw_) external virtual onlyRole(PeripheryRolesLib.STRATEGY_ADMIN) {
        BaseStrategyStorage.BaseStrategyStorage storage baseStrategyStorage = BaseStrategyStorage.fetch();
        emit MaxWithdrawUpdated(baseStrategyStorage.maxWithdraw, maxWithdraw_);
        baseStrategyStorage.maxWithdraw = maxWithdraw_;
    }

    /**
     * @dev Rescue function to recover tokens (only admin)
     * @dev Cannot rescue the strategy's asset token
     * @param token The token address to rescue
     * @param amount The amount to rescue (0 to rescue all available tokens)
     */
    function rescueToken(address token, uint256 amount) external virtual onlyRole(PeripheryRolesLib.STRATEGY_ADMIN) {
        BaseStrategyStorage.BaseStrategyStorage storage baseStrategyStorage = BaseStrategyStorage.fetch();
        if (token == baseStrategyStorage.asset) revert InvalidAsset();

        uint256 rescueAmount = amount == 0 ? IERC20(token).balanceOf(address(this)) : amount;
        IERC20(token).safeTransfer(msg.sender, rescueAmount);
    }

    /**
     * @notice Pauses the strategy, preventing deposits and withdrawals
     * @dev Only admin can pause the strategy
     */
    function pause() external onlyRole(PeripheryRolesLib.STRATEGY_ADMIN) {
        _pause();
    }

    /**
     * @notice Unpauses the strategy, allowing deposits and withdrawals
     * @dev Only admin can unpause the strategy
     */
    function unpause() external onlyRole(PeripheryRolesLib.STRATEGY_ADMIN) {
        _unpause();
    }
}
"
    },
    "src/periphery/lib/BaseStrategyStorageLib.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

library BaseStrategyStorageLib {
    /// @dev keccak256(abi.encode(uint256(keccak256("concrete.storage.BaseStrategyStorage")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant BaseStrategyStorageLocation =
        0xe84a5801edbad7de8e77ad0d2d730a53019bf3035b3c2f0ee45940fd7a547900;

    /// @custom:storage-location erc7201:concrete.storage.BaseStrategyStorage
    struct BaseStrategyStorage {
        /// @dev The underlying asset token
        address asset;
        /// @dev The single authorized vault address
        address vault;
        /// @dev The maximum amount that can be withdrawn from the strategy
        uint256 maxWithdraw;
    }

    /**
     * @dev Get the storage struct
     */
    function fetch() internal pure returns (BaseStrategyStorage storage $) {
        assembly {
            $.slot := BaseStrategyStorageLocation
        }
    }
}
"
    },
    "src/periphery/lib/MultisigStrategyStorageLib.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

/**
 * @title MultisigStrategyStorageLib
 * @dev Library for managing MultisigStrategy-specific storage
 * @dev Provides storage for multisig address, deposited amount, withdraw state, and operator
 */
library MultisigStrategyStorageLib {
    /// @dev Storage location for MultisigStrategyStorage keccak256(abi.encode(uint256(keccak256("concrete.storage.MultisigStrategyStorage")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant MULTISIG_STRATEGY_STORAGE_LOCATION =
        0xcb7da0d8897752a6df968d7ec6cb8f24f19b693fff4548ee51892258c2c21a00;

    /// @notice The MultisigStrategy storage structure
    struct MultisigStrategyStorage {
        address multiSig; // The address of the multi-signature wallet
        uint256 vaultDepositedAmount; // The amount of assets deposited into this strategy
    }

    /// @notice Fetches the MultisigStrategyStorage from the specified location
    /// @return $ The MultisigStrategyStorage struct
    function fetch() internal pure returns (MultisigStrategyStorage storage $) {
        bytes32 position = MULTISIG_STRATEGY_STORAGE_LOCATION;
        assembly {
            $.slot := position
        }
    }

    /// @notice Initializes the MultisigStrategyStorage
    /// @param multiSig_ The address of the multi-signature wallet
    function initialize(address multiSig_) internal {
        MultisigStrategyStorage storage $ = fetch();
        $.multiSig = multiSig_;
    }
}
"
    },
    "src/periphery/lib/PositionAccountingStorageLib.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

/**
 * @title PositionAccountingStorageLib
 * @dev Library for managing position accounting storage and validation
 * @dev Provides functionality for accounting change validation, cooldown periods, and thresholds
 */
library PositionAccountingStorageLib {
    /// @dev Storage location for keccak256(abi.encode(uint256(keccak256("concrete.storage.Positionaccounting.")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant POSITION_ACCOUNTING_STORAGE_LOCATION =
        0x5ce5a25f3602968dae3457825179f308a81a0ae9fafb34e4d83f623ffdb37f00;

    /// @notice The position accounting configuration structure
    struct PositionAccountingStorage {
        uint64 lastUpdatedTimestamp;
        uint64 cooldownPeriod; // in seconds
        uint64 maxAccountingChangeThreshold; // in basis points (10000 = 100%, 10 = 0.1%)
        uint64 accountingValidityPeriod; // in seconds
        uint256 accountingNonce;
    }

    /// @notice Fetches the PositionAccountingStorage from the specified location
    /// @return $ The PositionAccountingStorage struct
    function fetch() internal pure returns (PositionAccountingStorage storage $) {
        bytes32 position = POSITION_ACCOUNTING_STORAGE_LOCATION;
        assembly {
            $.slot := position
        }
    }

    /// @notice Initializes the PositionAccountingStorage with default values
    /// @param cooldownPeriod_ The update cooldown period in seconds
    /// @param maxAccountingChangeThreshold_ The maximum accounting change threshold in basis points
    /// @param accountingValidityPeriod_ The accounting validity period in seconds
    function initialize(uint64 cooldownPeriod_, uint64 maxAccountingChangeThreshold_, uint64 accountingValidityPeriod_)
        internal
    {
        PositionAccountingStorage storage $ = fetch();
        $.lastUpdatedTimestamp = uint64(block.timestamp);
        $.cooldownPeriod = cooldownPeriod_;
        $.maxAccountingChangeThreshold = maxAccountingChangeThreshold_;
        $.accountingValidityPeriod = accountingValidityPeriod_;
        $.accountingNonce = 0;
    }
}
"
    },
    "src/periphery/lib/PositionAccountingLib.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import {PositionAccountingStorageLib} from "./PositionAccountingStorageLib.sol";
import {SafeCast} from "@openzeppelin-contracts/utils/math/SafeCast.sol";

/**
 * @title PositionAccountingLib
 * @dev Library for managing position accounting operations and validation
 * @dev Provides functionality for accounting change validation, cooldown periods, and thresholds
 */
library PositionAccountingLib {
    using SafeCast for int256;

    /// @notice Basis points constant (10000 = 100%)
    uint256 public constant BASIS_POINTS = 10000;

    /// @notice Custom errors
    error InvalidMaxAccountingChangeThreshold();

    /// @notice Minimum difference between cooldown period and accounting validity period (60 seconds)
    uint64 private constant MIN_PERIOD_DIFFERENCE = 60;

    /// @notice Emitted when an accounting change is too large
    /// @param accountingNonce The accounting nonce of the accounting change
    /// @param diff yield(+) or loss(-) amount
    /// @param oldAccounting The accounting before the accounting change
    /// @param maxAccountingChangeThreshold The maximum allowed accounting change threshold (BASIS_POINTS = 100%, 100 = 1%)
    event AccountingChangeTooLarge(
        uint256 accountingNonce, int256 diff, uint256 oldAccounting, uint64 maxAccountingChangeThreshold
    );

    /// @notice Emitted when the update cooldown period is not passed
    /// @param accountingNonce The accounting nonce of the accounting change
    /// @param currentTimestamp The current timestamp
    /// @param coolDownTimestamp The cool down timestamp
    event CooldownPeriodNotPassed(uint256 accountingNonce, uint256 currentTimestamp, uint256 coolDownTimestamp);

    /// @notice Emitted when the max accounting change threshold is set
    /// @param oldMaxAccountingChangeThreshold The old max accounting change threshold
    /// @param maxAccountingChangeThreshold The new max accounting change threshold
    event MaxAccountingChangeThresholdSet(uint64 oldMaxAccountingChangeThreshold, uint64 maxAccountingChangeThreshold);

    /// @notice Emitted when the update cooldown period is set
    /// @param oldCooldownPeriod The old update cooldown period
    /// @param cooldownPeriod The new update cooldown period
    event SetCooldownPeriod(uint64 oldCooldownPeriod, uint64 cooldownPeriod);

    /// @notice Emitted when the accounting validity period is set
    /// @param oldAccountingValidityPeriod The old accounting validity period
    /// @param accountingValidityPeriod The new accounting validity period
    event SetAccountingValidityPeriod(uint64 oldAccountingValidityPeriod, uint64 accountingValidityPeriod);

    /// @notice Custom errors
    error InvalidAccountingNonce(uint256 provided, uint256 expected);
    error InsufficientUnderlyingBalance();
    error AccountingValidityPeriodExpired();
    error InvalidAccountingValidityPeriod();
    error InvalidCooldownPeriod();

    /**
     * @notice Sets the max accounting change threshold
     * @param maxAccountingChangeThreshold_ The maximum accounting change threshold in basis points (BASIS_POINTS = 100%, 100 = 1%)
     */
    function setMaxAccountingChangeThreshold(uint64 maxAccountingChangeThreshold_) internal {
        require(maxAccountingChangeThreshold_ <= BASIS_POINTS, InvalidMaxAccountingChangeThreshold());
        PositionAccountingStorageLib.PositionAccountingStorage storage $ = PositionAccountingStorageLib.fetch();
        uint64 oldThreshold = $.maxAccountingChangeThreshold;
        emit MaxAccountingChangeThresholdSet(oldThreshold, maxAccountingChangeThreshold_);
        $.maxAccountingChangeThreshold = maxAccountingChangeThreshold_;
    }

    /**
     * @notice Sets the accounting change validation period
     * @param accountingValidityPeriod_ The new accounting change validation period in seconds
     */
    function setAccountingValidityPeriod(uint64 accountingValidityPeriod_) internal {
        PositionAccountingStorageLib.PositionAccountingStorage storage $ = PositionAccountingStorageLib.fetch();
        if (accountingValidityPeriod_ < $.cooldownPeriod + MIN_PERIOD_DIFFERENCE) {
            revert InvalidAccountingValidityPeriod();
        }
        uint64 oldPeriod = $.accountingValidityPeriod;
        emit SetAccountingValidityPeriod(oldPeriod, accountingValidityPeriod_);
        $.accountingValidityPeriod = accountingValidityPeriod_;
    }

    /**
     * @notice Sets the accounting update cooldown period
     * @param cooldownPeriod_ The new update cooldown period in seconds
     */
    function setCooldownPeriod(uint64 cooldownPeriod_) internal {
        PositionAccountingStorageLib.PositionAccountingStorage storage $ = PositionAccountingStorageLib.fetch();
        if (cooldownPeriod_ > $.accountingValidityPeriod - MIN_PERIOD_DIFFERENCE) revert InvalidCooldownPeriod();
        uint64 oldPeriod = $.cooldownPeriod;
        emit SetCooldownPeriod(oldPeriod, cooldownPeriod_);
        $.cooldownPeriod = cooldownPeriod_;
    }

    /**
     * @notice Returns the next accounting nonce
     * @return The next accounting nonce
     */
    function getNextAccountingNonce() internal view returns (uint256) {
        return PositionAccountingStorageLib.fetch().accountingNonce + 1;
    }

    /**
     * @notice Returns the last updated timestamp
     * @return The last updated timestamp
     */
    function getLastUpdatedTimestamp() internal view returns (uint64) {
        PositionAccountingStorageLib.PositionAccountingStorage storage $ = PositionAccountingStorageLib.fetch();
        return $.lastUpdatedTimestamp;
    }

    /**
     * @notice Returns the max accounting change threshold
     * @return The max accounting change threshold
     */
    function getMaxAccountingChangeThreshold() internal view returns (uint64) {
        PositionAccountingStorageLib.PositionAccountingStorage storage $ = PositionAccountingStorageLib.fetch();
        return $.maxAccountingChangeThreshold;
    }

    /**
     * @notice Returns the accounting validity period
     * @return The accounting validity period
     */
    function getAccountingValidityPeriod() internal view returns (uint64) {
        PositionAccountingStorageLib.PositionAccountingStorage storage $ = PositionAccountingStorageLib.fetch();
        return $.accountingValidityPeriod;
    }

    /**
     * @notice Returns the update cooldown period
     * @return The update cooldown period
     */
    function getCooldownPeriod() internal view returns (uint64) {
        PositionAccountingStorageLib.PositionAccountingStorage storage $ = PositionAccountingStorageLib.fetch();
        return $.cooldownPeriod;
    }

    /**
     * @notice Updates the timestamp and nonce in the position accounting storage
     * @return newNonce The new nonce value after incrementing
     */
    function updateTimestampAndNonce() internal returns (uint256 newNonce) {
        PositionAccountingStorageLib.PositionAccountingStorage storage accounting$ =
            PositionAccountingStorageLib.fetch();
        accounting$.lastUpdatedTimestamp = uint64(block.timestamp);
        accounting$.accountingNonce += 1;
        return accounting$.accountingNonce;
    }

    /**
     * @notice Checks if the accounting change is valid
     */
    function _checkAccountingValidity() internal view {
        PositionAccountingStorageLib.PositionAccountingStorage storage $ = PositionAccountingStorageLib.fetch();
        if (block.timestamp - $.lastUpdatedTimestamp > $.accountingValidityPeriod) {
            revert AccountingValidityPeriodExpired();
        }
    }

    /**
     * @notice Validates if an accounting change is acceptable
     * @param diff the diff to adjust the accounting by
     * @param accountingNonce_ The expected accounting nonce
     * @param currentAccounting The current accounting amount
     * @return isValid True if the change is valid (no pause needed), false if should pause
     */
    function isValidAccountingChange(int256 diff, uint256 accountingNonce_, uint256 currentAccounting)
        internal
        returns (bool isValid)
    {
        PositionAccountingStorageLib.PositionAccountingStorage storage accounting$ =
            PositionAccountingStorageLib.fetch();
        if (accountingNonce_ != accounting$.accountingNonce + 1) {
            revert InvalidAccountingNonce(accountingNonce_, accounting$.accountingNonce + 1);
        }
        return _validateAccountingChange(diff, accountingNonce_, currentAccounting);
    }

    /**
     * @notice Checks if the change is acceptable
     * @param diff the diff to adjust the accounting by
     * @param accountingNonce_ The next accounting nonce of the accounting change
     * @param currentAccounting The current accounting value
     */
    function _validateAccountingChange(int256 diff, uint256 accountingNonce_, uint256 currentAccounting)
        internal
        returns (bool)
    {
        PositionAccountingStorageLib.PositionAccountingStorage storage accounting$ =
            PositionAccountingStorageLib.fetch();

        uint256 coolDownTimestamp = accounting$.lastUpdatedTimestamp + accounting$.cooldownPeriod;
        bool isCooldownPeriodPassed = block.timestamp >= coolDownTimestamp;
        if (!isCooldownPeriodPassed) {
            emit CooldownPeriodNotPassed(accountingNonce_, block.timestamp, coolDownTimestamp);
        }

        uint256 divergence = diff < 0 ? uint256(-diff) : uint256(diff);
        uint256 divergencePercentage = (divergence * BASIS_POINTS) / (currentAccounting + 1);
        bool isAcceptableAccountingChange = divergencePercentage <= accounting$.maxAccountingChangeThreshold;
        if (!isAcceptableAccountingChange) {
            emit AccountingChangeTooLarge(
                accountingNonce_, diff, currentAccounting, accounting$.maxAccountingChangeThreshold
            );
        }
        return isCooldownPeriodPassed && isAcceptableAccountingChange;
    }
}
"
    },
    "src/periphery/lib/PeripheryRolesLib.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

/**
 * @title PeripheryRolesLib
 * @dev Library containing role definitions for periphery contracts
 */
library PeripheryRolesLib {
    /// @dev Role for operators that can adjust total assets in strategies
    bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
    bytes32 public constant OPERATOR_ADMIN = keccak256("OPERATOR_ADMIN");

    /// @dev Role for strategy administrators
    bytes32 public constant STRATEGY_ADMIN = keccak256("STRATEGY_ADMIN");
    bytes32 public constant STRATEGY_ADMIN_ADMIN = keccak256("STRATEGY_ADMIN_ADMIN");
}
"
    },
    "src/interface/IStrategyTemplate.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

/**
 * @title IStrategyTemplate
 * @dev Interface that all strategy implementations must follow to be compatible with the vault system.
 * @dev Each strategy is bound to a single vault and manages that vault's funds in different protocols or investment opportunities.
 * @dev The Vault uses this interface to deploy, withdraw, and rebalance funds across multiple strategies.
 *
 * @notice This interface defines the core functionality required for strategy contracts:
 * - Asset management (allocation and deallocation of funds)
 * - Withdrawal capabilities for user redemptions
 * - Limit reporting for rebalancing operations
 * - Compatibility with the vault's underlying asset token
 *
 * @notice All strategies must implement proper access controls and ensure only authorized callers
 * (typically the vault) can execute fund management operations.
 *
 * @notice For strategies that accrue rewards from underlying protocols:
 * The vault has an arbitrary call execution function that can call any target with arbitrary data.
 * This is primarily used to claim rewards from external reward systems. Strategies that earn rewards
 * should provide dedicated functions that can be called by the vault through this mechanism to claim
 * rewards and forward them to the rewards distributor system.
 */

/**
 * @dev Enum representing different types of strategies
 */
enum StrategyType {
    ATOMIC, // 0: Strategy that executes operations atomically, provides on-chain accurate accounting of yield
    ASYNC, // 1: Strategy that requires asynchronous operations (multiple transactions), can provide stale (within defined latency) accounting of yield
    CROSSCHAIN // 2: Strategy that operates across different blockchain networks, can provide stale (within defined latency) accounting of yield

}

interface IStrategyTemplate {
    /**
     * @dev Allocates funds from the vault to the underlying protocol.
     * @dev This function will be called when the vault wants to deploy assets into the yield-generating protocol.
     *
     * @param data Arbitrary calldata that can be used to pass strategy-specific parameters for the allocation.
     *             This allows for flexible configuration of the allocation process (e.g., slippage tolerance,
     *             specific protocol parameters, routing information, etc.).
     *
     * - MUST emit the AllocateFunds event.
     * - MUST revert if all of assets cannot be deposited (due to allocation limit being reached, slippage, the protocol
     *   not being able to accept more funds, etc).
     *
     * NOTE: most implementations will require pre-approval of the Vault with the Vault's underlying asset token.
     */
    function allocateFunds(bytes calldata data) external returns (uint256);

    /**
     * @dev Deallocates funds from the underlying protocol back to the vault.
     * @dev This function will be called when the vault wants to withdraw assets from the yield-generating protocol.
     *
     * @param data Arbitrary calldata that can be used to pass strategy-specific parameters for the deallocation.
     *             This allows for flexible configuration of the withdrawal process (e.g., slippage tolerance,
     *             specific protocol parameters, withdrawal routing, etc.).
     *
     * - MUST emit the DeallocateFunds event.
     * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the protocol
     *   not having enough liquidity, etc).
     */
    function deallocateFunds(bytes calldata data) external returns (uint256);

    /**
     * @dev Sends assets of underlying tokens to sender.
     * @dev This function will be called when the vault unwinds its position while depositor withdraws.
     *
     * - MUST emit the Withdraw event.
     * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
     *   not having enough assets, etc).
     */
    function onWithdraw(uint256 assets) external returns (uint256);

    /**
     * @dev Rescue function to withdraw tokens that may have been accidentally sent to the strategy.
     * @dev This function allows authorized users to rescue tokens that are not part of the strategy's normal operations.
     *
     * @param token The address of the token to rescue.
     * @param amount The amount of tokens to rescue. Use 0 to rescue all available tokens.
     *
     * - MUST only allow rescue of tokens that are not the strategy's primary asset (asset()).
     * - MUST emit appropriate events for the rescue operation.
     * - MUST revert if the caller is not authorized to perform token rescue.
     * - MUST revert if attempting to rescue the strategy's primary asset token.
     */
    function rescueToken(address token, uint256 amount) external;

    /**
     * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
     *
     * - MUST be an ERC-20 token contract.
     * - MUST NOT revert.
     */
    function asset() external view returns (address);

    /**
     * @dev Returns the address of the vault that this strategy is bound to.
     *
     * - MUST return the vault address that was set during strategy initialization.
     * - MUST NOT revert.
     */
    function getVault() external view returns (address);

    /**
     * @dev Returns the type of strategy implementation.
     * @dev This function indicates the operational characteristics of the strategy.
     *
     * @return The strategy type as defined in the StrategyType enum.
     *
     * - MUST return one of the defined StrategyType values.
     * - MUST NOT revert.
     * - ATOMIC: Strategy executes operations atomically in the same transaction, yield MUST be always atomicly updated in strategy allocated amount.
     * - ASYNC: Strategy requires asynchronous operations across multiple transactions, yield Can be updated asynchronously within documented latency.
     * - CROSSCHAIN: Strategy operates across different blockchain networks, yield Can be updated asynchronously within documented latency.
     */
    function strategyType() external view returns (StrategyType);

    /**
     * @dev Returns the total value of assets that the bound vault has allocated in the strategy.
     * @dev This function is mainly used during yield accrual operations to account for strategy yield or losses.
     *
     * @return The total value of allocated assets denominated in the asset() token.
     *
     * - MUST return the total value of assets that the bound vault has allocated to this strategy.
     * - MUST account for any losses or depreciation in the underlying protocol.
     * - MUST NOT revert.
     * - MUST return 0 if the vault has no funds allocated to this strategy.
     */
    function totalAllocatedValue() external view returns (uint256);

    /**
     * @dev Returns the maximum amount of assets that can be allocated to the underlying protocol.
     * @dev This function is primarily used by the Allocator to determine allocation limits when rebalancing funds.
     *
     * - MUST return the maximum amount of underlying assets that can be allocated in a single call to allocateFunds.
     * - MUST NOT revert.
     * - MAY return 0 if the protocol cannot accept any more funds.
     * - MAY return type(uint256).max if there is no practical limit.
     */
    function maxAllocation() external view returns (uint256);

    /**
     * @dev Returns the maximum amount of assets that can be withdrawn from the strategy by the vault.
     * @dev This function is primarily used by the vault to determine withdrawal limits when covering user redemptions.
     *
     * - MUST return the maximum amount of underlying assets that can be withdrawn in a single call to onWithdraw.
     * - MUST NOT revert.
     * - MAY return 0 if no funds are available for withdrawal.
     * - SHOULD reflect current liquidity constraints and strategy-specific withdrawal limits.
     */
    function maxWithdraw() external view returns (uint256);
}
"
    },
    "node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
    },
    "node_modules/@openzeppelin/contracts/interfaces/IERC4626.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC4626.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../token/ERC20/IERC20.sol";
import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol";

/**
 * @dev Interface of the ERC-4626 "Tokenized Vault Standard", as defined in
 * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
 */
interface IERC4626 is IERC20, IERC20Metadata {
    event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);

    event Withdraw(
        address indexed sender,
        address indexed receiver,
        address indexed owner,
        uint256 assets,
        uint256 shares
    );

    /**
     * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
     *
     * - MUST be an ERC-20 token contract.
     * - MUST NOT revert.
     */
    function asset() external view returns (address assetTokenAddress);

    /**
     * @dev Returns the total amount of the underlying asset that is “managed” by Vault.
     *
     * - SHOULD include any compounding that occurs from yield.
     * - MUST be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT revert.
     */
    function totalAssets() external view returns (uint256 totalManagedAssets);

    /**
     * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
     * scenario where all the conditions are met.
     *
     * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT show any variations depending on the caller.
     * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
     * - MUST NOT revert.
     *
     * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
     * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
     * from.
     */
    function convertToShares(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
     * scenario where all the conditions are met.
     *
     * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT show any variations depending on the caller.
     * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
     * - MUST NOT revert.
     *
     * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
     * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
     * from.
     */
    function convertToAssets(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
     * through a deposit call.
     *
     * - MUST return a limited value if receiver is subject to some deposit limit.
     * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
     * - MUST NOT revert.
     */
    function maxDeposit(address receiver) external view returns (uint256 maxAssets);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
     * current on-chain conditions.
     *
     * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
     *   call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
     *   in the same transaction.
     * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
     *   deposit would be accepted, regardless if the user has enough tokens approved, etc.
     * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by depositing.
     */
    function previewDeposit(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
     *
     * - MUST emit the Deposit event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   deposit execution, and are accounted for during deposit.
     * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
     *   approving enough underlying tokens to the Vault contract, etc).
     *
     * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
     */
    function deposit(uint256 assets, address receiver) external returns (uint256 shares);

    /**
     * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
     * - MUST return a limited value if receiver is subject to some mint limit.
     * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
     * - MUST NOT revert.
     */
    function maxMint(address receiver) external view returns (uint256 maxShares);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
     * current on-chain conditions.
     *
     * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
     *   in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
     *   same transaction.
     * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
     *   would be accepted, regardless if the user has enough tokens approved, etc.
     * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by minting.
     */
    function previewMint(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
     *
     * - MUST emit the Deposit event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
     *   execution, and are accounted for during mint.
     * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
     *   approving enough underlying tokens to the Vault contract, etc).
     *
     * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
     */
    function mint(uint256 shares, address receiver) external returns (uint256 assets);

    /**
     * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
     * Vault, through a withdraw call.
     *
     * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
     * - MUST NOT revert.
     */
    function maxWithdraw(address owner) external view returns (uint256 maxAssets);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
     * given current on-chain conditions.
     *
     * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
     *   call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
     *   called
     *   in the same transaction.
     * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
     *   the withdrawal would be accepted, regardless if the user has enough shares, etc.
     * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by depositing.
     */
    function previewWithdraw(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
     *
     * - MUST emit the Withdraw event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   withdraw execution, and are accounted for during withdraw.
     * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
     *   not having enough shares, etc).
     *
     * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
     * Those methods should be performed separately.
     */
    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);

    /**
     * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
     * through a redeem call.
     *
     * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
     * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
     * - MUST NOT revert.
     */
    function maxRedeem(address owner) external view returns (uint256 maxShares);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
     * given current on-chain conditions.
     *
     * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
     *   in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
     *   same transaction.
     * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
     *   redemption would be accepted, regardless if the user has enough shares, etc.
     * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by redeeming.
     */
    function previewRedeem(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
     *
     * - MUST emit the Withdraw event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   redeem execution, and are accounted for during redeem.
     * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
     *   not having enough shares, etc).
     *
     * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
     * Those methods should be performed separately.
     */
    function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}
"
    },
    "node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../i

Tags:
ERC20, ERC165, Multisig, Mintable, Pausable, Liquidity, Yield, Upgradeable, Multi-Signature, Factory|addr:0x54f498a6f391ac48723f1e11875083aa24cbba8e|verified:true|block:23599310|tx:0x1d6a20262b4cb61c3d85d63f081c32b60171791ed46126a93e16d8c7c1952f3d|first_check:1760771280

Submitted on: 2025-10-18 09:08:03

Comments

Log in to comment.

No comments yet.