HourglassStableVaultKYC

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/HourglassStableVaultKYC.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;

import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { HourglassStableVaultBridgeManager } from "./base/HourglassStableVaultBridgeManager.sol";
import { HourglassStableVaultDepositCapManager } from "./base/HourglassStableVaultDepositCapManager.sol";
import { HourglassStableVaultDepositWindowManager } from "./base/HourglassStableVaultDepositWindowManager.sol";
import { HourglassStableVaultKYCManager } from "./base/HourglassStableVaultKYCManager.sol";
import { HourglassStableVaultTreasuryManager } from "./base/HourglassStableVaultTreasuryManager.sol";
import { IBridgeableVault } from "./interfaces/IBridgeableVault.sol";

enum OperationalMode {
    Deposit,
    Kyc,
    Yield,
    Withdraw,
    Recovery
}

/**
 * @title HourglassStableVaultKYC
 * @author Hourglass
 * @notice ERC20 pre-deposit vault with KYC for Stable. Accepts USDC deposits, deploys to treasury for yield generation
 * @dev This vault is a KYC-enabled vault that generates yield for KYC users only.
 *
 * Phase-by-phase logic:
 * 1. Deposit Phase:
 *    - Users receive shares at a 1:1 ratio
 *
 * 2. KYC Phase:
 *    - Admin can mark users as KYC approved during the KYC phase
 *    - Non-KYC users can recover their USDC at a 1:1 ratio
 *
 * 3. Yield Phase:
 *    - Treasury can withdraw KYC-approved USDC to treasury address
 *    - Non-KYC users can recover their USDC at a 1:1 ratio
 *
 * 4. Withdraw Phase:
 *    - KYC users can withdraw pro-rata USDT via the bridge contract
 *    - Non-KYC users can recover their USDC at a 1:1 ratio
 *
 * 5. Recovery Phase:
 *    - After RECOVERY_TIMESTAMP, anyone can transition to Recovery mode
 *    - Non-KYC users recover their USDC at a 1:1 ratio
 *    - KYC users recover their USDT at a pro-rata ratio plus any undeployed USDC pro-rata
 *
 * Accounting State:
 * - All deposited USDC is initially non-KYC
 * - When a user is marked KYC, their USDC moves from non-KYC pool to KYC pool
 * - Deployable assets are USDC in the KYC pool
 * - Non-KYC recoveries pull from the non-KYC USDC pool
 * - KYC recoveries pull from the USDT balance and undeployed USDC
 */
contract HourglassStableVaultKYC is
    IBridgeableVault,
    ERC20,
    ERC20Permit,
    AccessControl,
    ReentrancyGuard,
    HourglassStableVaultDepositWindowManager,
    HourglassStableVaultDepositCapManager,
    HourglassStableVaultBridgeManager,
    HourglassStableVaultKYCManager,
    HourglassStableVaultTreasuryManager
{
    using SafeERC20 for IERC20;
    using Math for uint256;

    // ------------------------------------------
    // State Variables
    // ------------------------------------------

    /// @notice USDC token contract address on Ethereum mainnet
    IERC20 public constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);

    /// @notice USDT token contract address on Ethereum mainnet
    IERC20 public constant USDT = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7);

    /// @notice Number of decimals for the vault shares (matches USDC/USDT)
    uint8 private constant DECIMALS = 6;

    /// @notice Role identifier for admin operations
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");

    /// @notice Role identifier for treasury operations
    bytes32 public constant TREASURY_ROLE = keccak256("TREASURY_ROLE");

    /// @notice Maximum number of users that can be processed in a single batch operation
    uint256 public constant MAX_KYC_BATCH_SIZE = 100;

    /// @notice Timestamp after which the vault can enter Recovery mode permissionlessly
    /// @dev Set to 180 days from deployment
    uint256 public immutable RECOVERY_TIMESTAMP;

    /// @notice Current operational mode of the vault
    /// @dev Starts in Deposit mode and transitions through phases
    OperationalMode private _mode = OperationalMode.Deposit;

    // sharesKyc
    // sharesNonKyc

    /// @notice Total shares held by non-KYC users
    /// @dev Decreases when users are marked as KYC approved
    uint256 public sharesNonKyc;

    /// @notice Total shares held by KYC-approved users
    /// @dev Increases when users are marked as KYC approved
    uint256 public sharesKyc;

    /// @notice Total USDC assets available for deployment from KYC pool
    /// @dev Decreases when treasury withdraws for yield generation
    uint256 public usdcKycDeployable;

    // ------------------------------------------
    // Events
    // ------------------------------------------

    /**
     * @notice Emitted when the operational mode changes
     * @param previousMode The previous operational mode
     * @param newMode The new operational mode
     */
    event OperationalModeChanged(OperationalMode indexed previousMode, OperationalMode indexed newMode);

    /**
     * @notice Emitted when treasury withdraws funds
     * @param to Recipient address
     * @param amount Amount withdrawn
     */
    event TreasuryWithdrawal(address indexed to, uint256 indexed amount);

    /**
     * @notice Emitted when non-KYC user recovers their funds
     * @param user User address
     * @param usdcAmount Amount of USDC recovered
     * @param shares Amount of shares burned
     */
    event NonKYCRecovery(address indexed user, uint256 indexed usdcAmount, uint256 shares);

    /**
     * @notice Emitted when KYC user recovers both USDT and undeployed USDC in recovery mode
     * @param user User address
     * @param usdtAmount Amount of USDT recovered
     * @param usdcAmount Amount of undeployed USDC recovered
     * @param shares Amount of shares burned
     */
    event KYCRecovery(address indexed user, uint256 indexed usdtAmount, uint256 usdcAmount, uint256 shares);

    /**
     * @notice Emitted when KYC user recovers USDT in withdrawal mode
     * @param sender The address initiating the redeem
     * @param receiver The address receiving the USDT
     * @param owner The owner of the shares being redeemed
     * @param usdtAmount Amount of USDT recovered
     * @param shares Amount of shares burned
     */
    event KYCRedeem(
        address indexed sender, address indexed receiver, address indexed owner, uint256 usdtAmount, uint256 shares
    );

    /**
     * @notice Emitted when ERC20 tokens are recovered from the contract
     * @param token The token address
     * @param to The recipient address
     * @param amount The amount of tokens recovered
     */
    event RecoveredERC20(address indexed token, address indexed to, uint256 indexed amount);

    /**
     * @notice Emitted when a deposit occurs
     * @param sender The address initiating the deposit
     * @param owner The address that will own the shares
     * @param shares Amount of shares minted
     */
    event Deposit(address indexed sender, address indexed owner, uint256 shares);

    // ------------------------------------------
    // Errors
    // ------------------------------------------

    /// @notice Thrown when attempting an operation without sufficient balance
    error InsufficientBalance();

    /// @notice Thrown when attempting to recover forbidden token
    error RecoveryForbidden(address token);

    /// @notice Thrown when attempting to recover zero amount of tokens or ETH
    error RecoverZeroAmount();

    /// @notice Thrown when an amount parameter is zero but must be non-zero
    error ZeroAmount();

    /// @notice Thrown when an address parameter is zero but must be non-zero
    error ZeroAddress();

    /// @notice Thrown when vault is not in the expected operational mode
    error InvalidOperationalMode(OperationalMode currentMode, OperationalMode expectedMode);

    /// @notice Thrown when trying to transition to Recovery mode before the recovery timestamp
    error TransitionToRecoveryFailed();

    /// @notice Thrown when there are insufficient funds for deployment to treasury
    error InsufficientFundsForDeployment();

    /// @notice Thrown when attempting to transfer shares outside of allowed conditions
    error TransfersDisabled();

    /// @notice Thrown when user has no shares
    error NoSharesFound();

    /// @notice Thrown when batch array is empty
    error EmptyBatch();

    /// @notice Thrown when batch size exceeds maximum allowed
    error BatchTooLarge(uint256 provided, uint256 max);

    // ------------------------------------------
    // Modifiers
    // ------------------------------------------

    /**
     * @notice Ensures the vault is in the expected operational mode
     * @param expectedMode The operational mode that the vault must be in
     */
    modifier onlyMode(OperationalMode expectedMode) {
        if (_mode != expectedMode) revert InvalidOperationalMode(_mode, expectedMode);
        _;
    }

    /**
     * @notice Ensures the provided address is not zero address
     * @param addr The address to validate
     */
    modifier onlyNonZeroAddress(address addr) {
        if (addr == address(0)) revert ZeroAddress();
        _;
    }

    /**
     * @notice Ensures the provided amount is not zero
     * @param amount The amount to validate
     */
    modifier onlyNonZeroAmount(uint256 amount) {
        if (amount == 0) revert ZeroAmount();
        _;
    }

    // ------------------------------------------
    // Constructor & Receive
    // ------------------------------------------

    /**
     * @notice Vault constructor
     * @param _admin Address to receive all administrative roles
     * @param _name Name of the vault token
     * @param _symbol Symbol of the vault token
     * @param _depositCap The initial deposit cap for USDC deposits
     * @param _depositStart Unix timestamp when deposits open (inclusive)
     * @param _depositEnd Unix timestamp when deposits close (inclusive)
     * @param _treasuryAddress Address of the treasury for yield deployment
     * @dev Bridge contract for withdrawals set later to avoid circular dependency.
     *      Sets RECOVERY_TIMESTAMP to 180 days from deployment.
     *      Grants DEFAULT_ADMIN_ROLE, ADMIN_ROLE, and TREASURY_ROLE to _admin.
     */
    constructor(
        address _admin,
        string memory _name,
        string memory _symbol,
        uint256 _depositCap,
        uint64 _depositStart,
        uint64 _depositEnd,
        address _treasuryAddress
    )
        HourglassStableVaultDepositWindowManager(_depositStart, _depositEnd)
        HourglassStableVaultDepositCapManager(_depositCap)
        HourglassStableVaultTreasuryManager(_treasuryAddress)
        ERC20(_name, _symbol)
        ERC20Permit(_name)
        onlyNonZeroAddress(_admin)
    {
        _grantRole(DEFAULT_ADMIN_ROLE, _admin);
        _grantRole(ADMIN_ROLE, _admin);
        _grantRole(TREASURY_ROLE, _admin);

        RECOVERY_TIMESTAMP = block.timestamp + 180 days;
    }

    // ------------------------------------------
    // External Functions - Admin
    // ------------------------------------------

    /**
     * @notice Transitions the vault from Deposit to Kyc mode
     * @dev Only callable by admin role when in Deposit mode.
     *      After transition, no new deposits are accepted.
     */
    function transitionToKycMode() external onlyRole(ADMIN_ROLE) onlyMode(OperationalMode.Deposit) {
        _transitionToModeUnsafe(OperationalMode.Kyc);
    }

    /**
     * @notice Transitions the vault from KYC to Yield mode
     * @dev Called after KYC verification is complete.
     *      Only callable by admin role when in Kyc mode.
     *      After transition, treasury can withdraw KYC-approved funds.
     */
    function transitionToYieldMode() external onlyRole(ADMIN_ROLE) onlyMode(OperationalMode.Kyc) {
        _transitionToModeUnsafe(OperationalMode.Yield);
    }

    /**
     * @notice Transitions the vault from Yield to Withdraw mode
     * @dev Called after yield generation is complete and USDT has been returned.
     *      Only callable by admin role when in Yield mode.
     *      Treasury should have returned USDT before this transition.
     *      After transition, KYC users can withdraw via bridge.
     */
    function transitionToWithdrawMode() external onlyRole(ADMIN_ROLE) onlyMode(OperationalMode.Yield) {
        _transitionToModeUnsafe(OperationalMode.Withdraw);
    }

    /**
     * @notice Transitions the vault from any state to Recovery mode (a terminal state)
     * @dev Must be past the recovery timestamp. Can be called permissionlessly.
     *      Recovery mode is a terminal state that cannot be exited.
     *      Provides emergency exit for all users if admin is compromised.
     *      Non-KYC users recover USDC at 1:1, KYC users recover USDT + undeployed USDC.
     */
    function transitionToRecoveryMode() external {
        if (block.timestamp < RECOVERY_TIMESTAMP) revert TransitionToRecoveryFailed();
        _transitionToModeUnsafe(OperationalMode.Recovery);
    }

    /**
     * @notice Modifies a deposit window that is pending (has not yet started)
     * @param newStart Unix timestamp when deposits open
     * @param newEnd Unix timestamp when deposits close
     */
    function modifyPendingDepositWindow(
        uint64 newStart,
        uint64 newEnd
    )
        external
        onlyRole(ADMIN_ROLE)
        onlyMode(OperationalMode.Deposit)
    {
        _modifyPendingDepositWindow(newStart, newEnd);
    }

    /**
     * @notice Modifies the end timestamp of a deposit window that has already started
     * @param newEnd Unix timestamp when deposits close
     */
    function modifyStartedDepositWindow(uint64 newEnd)
        external
        onlyRole(ADMIN_ROLE)
        onlyMode(OperationalMode.Deposit)
    {
        _modifyStartedDepositWindow(newEnd);
    }

    /**
     * @notice Updates the deposit cap for the vault
     * @param newCap New maximum total USDT allowed (must be > 0 and >= current deposits)
     */
    function setDepositCap(uint256 newCap) external onlyRole(ADMIN_ROLE) onlyMode(OperationalMode.Deposit) {
        _setDepositCap(sharesNonKyc, newCap);
    }

    /**
     * @notice Sets the authorized bridge contract address and marks it as KYC-approved
     * @param newBridgeContract Address of the bridge contract that can call withdraw
     * @dev Also marks the bridge as KYC-approved to allow it to redeem shares
     */
    function setBridgeContract(address newBridgeContract) external onlyRole(ADMIN_ROLE) {
        _setBridgeContract(newBridgeContract);
        _setKycStatus(newBridgeContract, true);
    }

    /**
     * @notice Sets the treasury address where funds are sent
     * @param newTreasury Address of the new treasury
     */
    function setTreasuryAddress(address newTreasury) external onlyRole(ADMIN_ROLE) {
        _setTreasuryAddress(newTreasury);
    }

    /**
     * @notice Batch set KYC status for multiple users
     * @param users Array of addresses to update (max 100)
     * @param status KYC status to set
     * @dev Reverts if batch is empty or exceeds MAX_BATCH_SIZE
     */
    function batchSetKycStatus(
        address[] calldata users,
        bool status
    )
        external
        onlyRole(ADMIN_ROLE)
        onlyMode(OperationalMode.Kyc)
    {
        uint256 length = users.length;

        // Validate batch size
        if (length == 0) revert EmptyBatch();
        if (length > MAX_KYC_BATCH_SIZE) revert BatchTooLarge(length, MAX_KYC_BATCH_SIZE);

        for (uint256 i = 0; i < length;) {
            address user = users[i];
            if (user == address(0)) revert ZeroAddress();
            _setKycStatusWithAccounting(user, status);
            ++i;
        }
    }

    /**
     * @notice Recovers mistakenly sent ERC20 tokens from the contract
     * @param token Address of the ERC20 token to recover
     * @param to Recipient address for recovered tokens
     * @dev Only recovers excess tokens beyond what users can claim.
     *      Protects sharesNonKyc USDC for non-KYC users.
     *      Protects usdcKycDeployable USDC for KYC users.
     *      Protects all USDT as it belongs to KYC users.
     */
    function recoverErc20(
        address token,
        address to
    )
        external
        onlyRole(ADMIN_ROLE)
        onlyNonZeroAddress(token)
        onlyNonZeroAddress(to)
        nonReentrant
    {
        uint256 balance = IERC20(token).balanceOf(address(this));
        uint256 protected = 0;

        // Compute protected amounts for assets the contract holds
        if (token == address(USDC)) {
            // must protect sharesNonKyc so non-KYC users can recover their assets
            // must protect usdcKycDeployable since it belongs to the kyc users
            protected = sharesNonKyc + usdcKycDeployable;
        } else if (token == address(USDT)) {
            // all usdt belongs to the kyc users
            revert RecoveryForbidden(address(USDT));
        }

        uint256 recoverable = balance > protected ? balance - protected : 0;
        if (recoverable == 0) revert RecoverZeroAmount();

        IERC20(token).safeTransfer(to, recoverable);
        emit RecoveredERC20(token, to, recoverable);
    }

    // ---------------------
    // Treasury Functions
    // ---------------------

    /**
     * @notice Allows treasury to transfer USDC funds during Yield phase
     * @param amount Amount of USDC to transfer to treasury for yield generation
     * @dev Treasury can only access KYC pool funds, not non-KYC assets.
     *      Reduces usdcKycDeployable to track deployed funds.
     *      Ensures non-KYC user funds remain untouched and recoverable.
     */
    function transferToTreasury(uint256 amount)
        external
        nonReentrant
        onlyRole(TREASURY_ROLE)
        onlyMode(OperationalMode.Yield)
        onlyWhenTreasurySet
        onlyNonZeroAmount(amount)
    {
        if (amount > usdcKycDeployable) {
            revert InsufficientFundsForDeployment();
        }

        uint256 usdcBalance = USDC.balanceOf(address(this));
        if (amount > usdcBalance) {
            revert InsufficientFundsForDeployment();
        }

        usdcKycDeployable -= amount;
        USDC.safeTransfer(_treasuryAddress, amount);
        emit TreasuryWithdrawal(_treasuryAddress, amount);
    }

    // ------------------------------------------
    // Public Functions
    // ------------------------------------------

    /**
     * @notice Returns the current operational mode of the vault
     * @return The current operational mode of the vault
     */
    function operationalMode() public view returns (OperationalMode) {
        return _mode;
    }

    /**
     * @notice Returns the decimals of the vault shares
     * @return decimals The number of decimals (6, same as USDC and USDT)
     */
    function decimals() public pure override(ERC20) returns (uint8) {
        return DECIMALS;
    }

    /**
     * @notice Deposits USDC and mints shares for non-KYC users
     * @param assets Amount of USDC to deposit
     * @param receiver Address to receive the shares
     * @dev Follows ERC4626-style pattern: check → preview → execute.
     *      Shares are minted at 1:1 ratio to prevent inflation attacks.
     *      Deposits are subject to the configured deposit cap.
     */
    function deposit(
        uint256 assets,
        address receiver
    )
        public
        nonReentrant
        onlyMode(OperationalMode.Deposit)
        onlyWhenDepositWindowOpen
        onlyNonZeroAmount(assets)
    {
        // 1. Check against maximum
        if (assets > maxDeposit()) {
            revert DepositExceedsCap();
        }

        // 1. Transfer assets from depositor
        USDC.safeTransferFrom(msg.sender, address(this), assets);

        // 2. Update accounting
        sharesNonKyc += assets;

        // 3. Mint shares to receiver
        _mint(receiver, assets);

        // 4. Emit event
        emit Deposit(msg.sender, receiver, assets);
    }

    /**
     * @notice Allows non-KYC users to redeem shares for USDC at 1:1 ratio
     * @param shares Amount of shares to burn
     * @param receiver Address to receive the USDC
     * @param owner Owner of the shares being redeemed
     * @dev Available in any phase (including Deposit) - non-KYC funds are always protected.
     *      Non-KYC users maintain 1:1 redemption rate throughout all phases.
     *      This ensures principal preservation for users who don't complete KYC.
     */
    function redeemNonKyc(
        uint256 shares,
        address receiver,
        address owner
    )
        public
        nonReentrant
        onlyNonZeroAmount(shares)
        onlyNonKyc(owner)
    {
        // 1. Handle allowance if caller is not owner
        if (msg.sender != owner) {
            _spendAllowance(owner, msg.sender, shares);
        }

        // 2. Update accounting
        sharesNonKyc -= shares;

        // 3. Burn shares from owner
        _burn(owner, shares);

        // 4. Transfer USDC to receiver
        USDC.safeTransfer(receiver, shares);

        // 5. Emit events
        emit NonKYCRecovery(owner, shares, shares);
    }

    /**
     * @notice Allows KYC users to redeem shares for both USDT and undeployed USDC in Recovery mode
     * @param shares Amount of shares to redeem
     * @param receiver Address to receive the assets
     * @param owner Owner of the shares being redeemed
     * @dev Can only be called in Recovery mode by KYC approved users.
     *      Returns pro-rata share of both USDT and any undeployed USDC.
     *      Non-KYC users should use redeemNonKyc() which works in any phase.
     *      This function ensures KYC users can recover all assets in emergency scenarios,
     *      including both yield-bearing USDT and any USDC not yet deployed.
     */
    function redeemRecoveryKyc(
        uint256 shares,
        address receiver,
        address owner
    )
        external
        nonReentrant
        onlyMode(OperationalMode.Recovery)
        onlyNonZeroAmount(shares)
        onlyKycApproved(owner)
    {
        if (msg.sender != owner) {
            _spendAllowance(owner, msg.sender, shares);
        }

        // 2. Calculate both USDT and undeployed USDC amounts
        (uint256 usdtOut, uint256 usdcOut,) = previewRedeem(owner, shares);

        // 4. Update accounting
        sharesKyc -= shares;
        if (usdcOut > 0) {
            usdcKycDeployable -= usdcOut;
        }

        // 5. Burn shares
        _burn(owner, shares);

        // 6. Transfer both assets
        if (usdtOut > 0) {
            USDT.safeTransfer(receiver, usdtOut);
        }
        if (usdcOut > 0) {
            USDC.safeTransfer(receiver, usdcOut);
        }

        // 7. Emit event
        emit KYCRecovery(owner, usdtOut, usdcOut, shares);
    }

    // ------------------------------------------
    // Preview Functions (ERC4626-style)
    // ------------------------------------------

    function previewRedeem(
        address user,
        uint256 shares
    )
        public
        view
        returns (uint256 usdtOut, uint256 usdcOut, bool isKyc)
    {
        if (shares > balanceOf(user)) {
            revert InsufficientBalance();
        }
        isKyc = _isKycApproved(user);
        if (isKyc) {
            usdtOut = _convertToAssets(shares, sharesKyc, USDT.balanceOf(address(this)));
            usdcOut = _convertToAssets(shares, sharesKyc, usdcKycDeployable);
        } else {
            usdtOut = 0;
            usdcOut = shares;
        }
    }

    // ------------------------------------------
    // Max Functions (ERC4626-style)
    // ------------------------------------------

    /**
     * @notice Maximum amount of USDC that can be deposited
     * @return maxAssets Maximum USDC that can be deposited
     */
    function maxDeposit() public view returns (uint256 maxAssets) {
        if (_mode != OperationalMode.Deposit || !_depositWindowOpen()) return 0;
        return _getMaxDepositAgainstCap(sharesNonKyc);
    }

    // ------------------------------------------
    // Transfer Override Functions
    // ------------------------------------------
    // IBridgeableVault Implementation
    // ------------------------------------------

    /**
     * @notice Redeems vault shares for underlying assets for bridge operations
     * @param shares Amount of vault shares to redeem
     * @param receiver Address to receive the redeemed assets
     * @param owner Address that owns the shares being redeemed
     * @return usdtOut Amount of USDT returned to the receiver
     * @dev Implements IBridgeableVault interface for bridge withdrawals
     * @dev Can only be called in Withdraw mode by authorized bridge contract
     * @dev KYC users receive pro-rata share of USDT based on their share ownership
     */
    function redeemBridge(
        uint256 shares,
        address receiver,
        address owner
    )
        external
        override
        nonReentrant
        onlyNonZeroAmount(shares)
        onlyCallerIsBridge
        onlyMode(OperationalMode.Withdraw)
        returns (uint256 usdtOut)
    {
        // Preview conversion
        (usdtOut,,) = previewRedeem(owner, shares);
        if (usdtOut == 0) revert ZeroAmount();

        // Handle allowance if caller is not owner
        if (msg.sender != owner) {
            _spendAllowance(owner, msg.sender, shares);
        }

        // Update accounting
        sharesKyc -= shares;

        // Burn shares from owner
        _burn(owner, shares);

        // Transfer USDT to receiver
        USDT.safeTransfer(receiver, usdtOut);

        // Emit event
        emit KYCRedeem(msg.sender, receiver, owner, usdtOut, shares);
    }

    /**
     * @notice Preview the amount of assets that would be received for bridge redemption
     * @param shares Amount of shares to preview redemption for
     * @return assets Amount of USDT that would be received
     * @dev Implements IBridgeableVault interface
     * @dev Uses the KYC-specific previewRedeem with msg.sender for compatibility
     */
    function previewRedeemBridge(uint256 shares) external view override returns (uint256 assets) {
        // Use the KYC-specific previewRedeem function with msg.sender
        // This will check if the caller is KYC'd and return appropriate amounts
        (uint256 usdtOut,,) = previewRedeem(msg.sender, shares);
        return usdtOut;
    }

    // ------------------------------------------

    /**
     * @notice Override internal _update to restrict share transfers
     * @param from Sender address
     * @param to Recipient address
     * @param value Amount of shares to transfer
     * @dev Only KYC users can transfer to bridge during Withdraw phase
     *      Mints and burns (from or to address(0)) are always allowed
     */
    function _update(address from, address to, uint256 value) internal virtual override {
        if (
            from == address(0) || to == address(0)
                || (_mode == OperationalMode.Withdraw && _isKycApproved(from) && to == _bridgeContract)
        ) {
            super._update(from, to, value);
            return;
        }
        revert TransfersDisabled();
    }

    // ------------------------------------------
    // Internal Functions
    // ------------------------------------------

    /**
     * @notice Internal helper to convert shares to assets (always rounds down)
     * @param shares Amount of shares to convert
     * @param supply Current supply of shares in the pool
     * @param totalAssets Current total assets in the pool
     * @return Amount of assets equivalent to the shares
     */
    function _convertToAssets(uint256 shares, uint256 supply, uint256 totalAssets) internal pure returns (uint256) {
        if (supply == 0) return 0;
        return Math.mulDiv(shares, totalAssets, supply, Math.Rounding.Floor);
    }

    /**
     * @notice Transitions the vault to a new operational mode
     * @param newMode The new operational mode
     * @dev It is the job of the caller to ensure the prevMode -> newMode transition is valid
     */
    function _transitionToModeUnsafe(OperationalMode newMode) private {
        OperationalMode prevMode = _mode;
        _mode = newMode;
        emit OperationalModeChanged(prevMode, newMode);
    }

    // ------------------------------------------
    // Internal Execution Functions (ERC4626-style)
    // ------------------------------------------

    /**
     * @notice Internal function to set KYC status and update accounting
     * @param user Address to update
     * @param status KYC status to set
     * @dev user validated to be non-zero address at call site
     */
    function _setKycStatusWithAccounting(address user, bool status) internal {
        uint256 userShares = balanceOf(user);
        if (userShares == 0) revert NoSharesFound();

        // Update KYC status using base contract function (emits KYCStatusSet event)
        // Note: _setKycStatus will revert if status is unchanged, guaranteeing the status is different
        _setKycStatus(user, status);

        // Update share and asset accounting based on status change
        // We know status changed, so we can simplify the logic
        if (status) {
            // Marking as KYC - move from non-KYC pool to KYC pool
            sharesNonKyc -= userShares;
            sharesKyc += userShares;
            usdcKycDeployable += userShares;
        } else {
            // Unmarking as KYC - move from KYC pool back to non-KYC pool
            sharesNonKyc += userShares;
            sharesKyc -= userShares;
            usdcKycDeployable -= userShares;
        }
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/access/AccessControl.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (access/AccessControl.sol)

pragma solidity ^0.8.20;

import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {IERC165, ERC165} from "../utils/introspection/ERC165.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```solidity
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```solidity
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
 * to enforce additional security measures for this role.
 */
abstract contract AccessControl is Context, IAccessControl, ERC165 {
    struct RoleData {
        mapping(address account => bool) hasRole;
        bytes32 adminRole;
    }

    mapping(bytes32 role => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with an {AccessControlUnauthorizedAccount} error including the required role.
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

    /// @inheritdoc IERC165
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view virtual returns (bool) {
        return _roles[role].hasRole[account];
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
     * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
     * is missing `role`.
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert AccessControlUnauthorizedAccount(account, role);
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
        return _roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleGranted} event.
     */
    function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleRevoked} event.
     */
    function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `callerConfirmation`.
     *
     * May emit a {RoleRevoked} event.
     */
    function renounceRole(bytes32 role, address callerConfirmation) public virtual {
        if (callerConfirmation != _msgSender()) {
            revert AccessControlBadConfirmation();
        }

        _revokeRole(role, callerConfirmation);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        bytes32 previousAdminRole = getRoleAdmin(role);
        _roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleGranted} event.
     */
    function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
        if (!hasRole(role, account)) {
            _roles[role].hasRole[account] = true;
            emit RoleGranted(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Attempts to revoke `role` from `account` and returns a boolean indicating if `role` was revoked.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleRevoked} event.
     */
    function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
        if (hasRole(role, account)) {
            _roles[role].hasRole[account] = false;
            emit RoleRevoked(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC-20
 * applications.
 */
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
    mapping(address account => uint256) private _balances;

    mapping(address account => mapping(address spender => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * Both values are immutable: they can only be set once during construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /// @inheritdoc IERC20
    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    /// @inheritdoc IERC20
    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `value`.
     */
    function transfer(address to, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, value);
        return true;
    }

    /// @inheritdoc IERC20
    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, value);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Skips emitting an {Approval} event indicating an allowance update. This is not
     * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `value`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `value`.
     */
    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, value);
        _transfer(from, to, value);
        return true;
    }

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _transfer(address from, address to, uint256 value) internal {
        if (from == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(from, to, value);
    }

    /**
     * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
     * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
     * this function.
     *
     * Emits a {Transfer} event.
     */
    function _update(address from, address to, uint256 value) internal virtual {
        if (from == address(0)) {
            // Overflow check required: The rest of the code assumes that totalSupply never overflows
            _totalSupply += value;
        } else {
            uint256 fromBalance = _balances[from];
            if (fromBalance < value) {
                revert ERC20InsufficientBalance(from, fromBalance, value);
            }
            unchecked {
                // Overflow not possible: value <= fromBalance <= totalSupply.
                _balances[from] = fromBalance - value;
            }
        }

        if (to == address(0)) {
            unchecked {
                // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
                _totalSupply -= value;
            }
        } else {
            unchecked {
                // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
                _balances[to] += value;
            }
        }

        emit Transfer(from, to, value);
    }

    /**
     * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
     * Relies on the `_update` mechanism
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _mint(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(address(0), account, value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead
     */
    function _burn(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        _update(account, address(0), value);
    }

    /**
     * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        _approve(owner, spender, value, true);
    }

    /**
     * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
     *
     * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
     * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
     * `Approval` event during `transferFrom` operations.
     *
     * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
     * true using the following override:
     *
     * ```solidity
     * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
     *     super._approve(owner, spender, value, true);
     * }
     * ```
     *
     * Requirements are the same as {_approve}.
     */
    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }

    /**
     * @dev Updates `owner`'s allowance for `spender` based on spent `value`.
     *
     * Does not update the allowance value in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Does not emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance < type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/ERC20Permit.sol)

pragma solidity ^0.8.20;

import {IERC20Permit} from "./IERC20Permit.sol";
import {ERC20} from "../ERC20.sol";
import {ECDSA} from "../../../utils/cryptography/ECDSA.sol";
import {EIP712} from "../../../utils/cryptography/EIP712.sol";
import {Nonces} from "../../../utils/Nonces.sol";

/**
 * @dev Implementation of the ERC-20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[ERC-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC-20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces {
    bytes32 private constant PERMIT_TYPEHASH =
        keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

    /**
     * @dev Permit deadline has expired.
     */
    error ERC2612ExpiredSignature(uint256 deadline);

    /**
     * @dev Mismatched signature.
     */
    error ERC2612InvalidSigner(address signer, address owner);

    /**
     * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
     *
     * It's a good idea to use the same `name` that is defined as the ERC-20 token name.
     */
    constructor(string memory name) EIP712(name, "1") {}

    /// @inheritdoc IERC20Permit
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        if (block.timestamp > deadline) {
            revert ERC2612ExpiredSignature(deadline);
        }

        bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));

        bytes32 hash = _hashTypedDataV4(structHash);

        address signer = ECDSA.recover(hash, v, r, s);
        if (signer != owner) {
            revert ERC2612InvalidSigner(signer, owner);
        }

        _approve(owner, spender, value);
    }

    /// @inheritdoc IERC20Permit
    function nonces(address owner) public view virtual override(IERC20Permit, Nonces) returns (uint256) {
        return super.nonces(owner);
    }

    /// @inheritdoc IERC20Permit
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {
        return _domainSeparatorV4();
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)

pragma solidity >=0.4.16;

/**
 * @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);
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

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

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly 

Tags:
ERC20, ERC165, Multisig, Yield, Upgradeable, Multi-Signature, Factory|addr:0xa2939446b8b0259ef70e66230cea27a6d8f1afec|verified:true|block:23735339|tx:0x356de7c5d98a3f2e2a3d56754859ef1708640edddb31497daa25bde7addbcce1|first_check:1762373029

Submitted on: 2025-11-05 21:03:50

Comments

Log in to comment.

No comments yet.