KYCOneWayVault

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/KYCOneWayVault.sol": {
      "content": "// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.28;

import {ERC4626Upgradeable} from "@openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin-contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import {BaseAccount} from "./BaseAccount.sol";
import {OwnableUpgradeable} from "@openzeppelin-contracts-upgradeable/access/OwnableUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin-contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {Initializable} from "@openzeppelin-contracts-upgradeable/proxy/utils/Initializable.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol";
import {IZkMe} from './IZkMe.sol';

/**
 * @title KYCOneWayVault
 * @dev A one-way vault contract that enables deposits on one chain with withdrawal requests
 * to be processed on another domain/chain. Uses ERC4626 tokenized vault standard.
 *
 * This vault handles:
 * - Deposits with fee collection
 * - Withdrawals with fee collection
 * - Strategist-controlled redemption rate updates
 * - Cross-domain withdrawal requests (one-way from source to destination)
 * - Fee distribution between platform and strategist
 * - Deposit caps
 * - Pausability
 */
contract KYCOneWayVault is
    Initializable,
    ERC4626Upgradeable,
    OwnableUpgradeable,
    ReentrancyGuardUpgradeable,
    UUPSUpgradeable
{
    using Math for uint256;

    /**
     * @dev bytes32(uint256(keccak256('kyc_one_way_vault.zkme_config')) - 1)
     */
    bytes32 internal constant _ZKME_CONFIG_SLOT = 0x6aa30f72c819d6ffdc11d4f73f6dbad58c3fe2ea745f3548ccd5eb0e816f9ff9;
    function _getZkMeConfig() private pure returns (ZkMeConfig storage $) {
        assembly {
            $.slot := _ZKME_CONFIG_SLOT
        }
    }

    /**
     * @dev bytes32(uint256(keccak256('kyc_one_way_vault.kyc_allowed_users')) - 1)
     */
    bytes32 internal constant _KYC_ALLOWED_USERS_SLOT = 0x100924d8e5ab1df33616bfd25eded8b01d3d68d2ade2d07beeff707eff244277;
    function _getKycAllowedUsers() private pure returns (KycAllowedUsers storage $) {
        assembly {
            $.slot := _KYC_ALLOWED_USERS_SLOT
        }
    }

    /**
     * @dev Emitted when the vault's paused state changes
     * @param paused New paused state
     */
    event PausedStateChanged(bool paused);

    /**
     * @dev Emitted when the vault is paused due to a stale redemption rate
     * @param lastUpdateTimestamp Timestamp of the last rate update
     * @param currentTimestamp Current timestamp when the pause was triggered
     */
    event StaleRatePaused(uint64 lastUpdateTimestamp, uint64 currentTimestamp);

    /**
     * @dev Emitted when the redemption rate is updated
     * @param newRate The updated redemption rate
     */
    event RateUpdated(uint256 newRate);

    /**
     * @dev Emitted when fees are distributed to strategist and platform accounts
     * @param strategistAccount Address receiving strategist portion of fees
     * @param platformAccount Address receiving platform portion of fees
     * @param strategistShares Amount of shares distributed to strategist
     * @param platformShares Amount of shares distributed to platform
     */
    event FeesDistributed(
        address indexed strategistAccount,
        address indexed platformAccount,
        uint256 strategistShares,
        uint256 platformShares
    );

    /**
     * @dev Emitted when a withdrawal request is created
     * @param id Unique ID for the withdrawal request
     * @param owner Address that owns the shares being withdrawn
     * @param receiver Address on destination domain to receive assets (as string)
     * @param shares Amount of shares being withdrawn
     */
    event WithdrawRequested(uint64 indexed id, address owner, string receiver, uint256 shares);

    /**
     * @dev Emitted when the vault configuration is updated
     * @param updater Address that updated the config
     * @param newConfig The new configuration
     */
    event ConfigUpdated(address indexed updater, KYCOneWayVaultConfig newConfig);

    /**
     * @dev Emitted when the zkMe configuration is updated
     * @param updater Address that updated the config
     * @param newConfig The new configuration
     */
    event ZkMeConfigUpdated(address indexed updater, ZkMeConfig newConfig);

    /**
     * @dev Emitted when the user is expliticly allowed or removed from explicitly allowed list
     * @param updater Address that updated user status
     * @param user Address, the explicit approval of which was altered
     * @param allowed Whether this address is explicitly allowed or not
     */
    event UserAllowed(address indexed updater, address indexed user, bool allowed);

    /**
     * @dev Restricts function access to only the strategist
     */
    modifier onlyStrategist() {
        if (msg.sender != config.strategist) {
            revert("Only strategist allowed");
        }
        _;
    }

    /**
     * @dev Restricts function access to only the owner or strategist
     */
    modifier onlyOwnerOrStrategist() {
        if (msg.sender != owner() && msg.sender != config.strategist) {
            revert("Only owner or strategist allowed");
        }
        _;
    }

    /**
     * @dev Ensures the vault is not paused
     */
    modifier whenNotPaused() {
        if (vaultState.paused) {
            revert("Vault is paused");
        }
        _;
    }

    /**
     * @dev Ensures the vault is not paused by a different reason than stale redemption rate
     */
    modifier whenNotManuallyPaused() {
        if (vaultState.paused && !vaultState.pausedByStaleRate) {
            revert("Vault is paused by owner or strategist");
        }
        _;
    }

    /**
     * @dev Ensures that a function can only be called by either explicitly approved users
     *      or by users who completed the zkMe KYC procedure
     */
    modifier onlyKyc() {
        // First, check if user is explicitly approved inside of the KYC vault.
        // Only then ask ZkMe if user is approved on its side.
        // This order of execution saves gas, avoiding a call to ZkMe when possible.
        if (
            !_getKycAllowedUsers().allowedUsers[msg.sender] &&
            !_getZkMeConfig().zkMe.hasApproved(_getZkMeConfig().cooperator, msg.sender)
        ) {
            revert KycFailed();
        }

        _;
    }

    /**
     * @dev Returned when user failed to complete KYC
     */
    error KycFailed();

    /**
     * @dev Configuration structure for zkMe KYC service
     * @param zkMe zkMe Verify & Certify contract address
     * @param cooperator Selector address provided by zkMe
     */
    struct ZkMeConfig {
        IZkMe zkMe;
        address cooperator;
    }

    /**
     * @dev Explicitly allowed users which don't have to go through KYC process
     * @param allowedUsers Mapping which stores true if a user is allowed
     */
    struct KycAllowedUsers {
        mapping(address => bool) allowedUsers;
    }

    /**
     * @dev Configuration structure for the vault
     * @param depositAccount Account where deposits are held
     * @param strategist Address of the vault strategist
     * @param depositFeeBps Fee charged on deposits in basis points (1 BPS = 0.01%)
     * @param withdrawFeeBps Fee charged on withdrawals in basis points (1 BPS = 0.01%)
     * @param maxRateIncrementBps Maximum allowed relative increase in redemption rate per update (in basis points).
     *     For example, if maxRateIncrementBps is 100 (1%), at a redemption rate of 200, the new rate can go up to 202.
     * @param maxRateDecrementBps Maximum allowed relative decrease in redemption rate per update (in basis points).
     *     For example, if maxRateDecrementBps is 50 (0.5%), at a redemption rate of 200, the new rate can go down to 199
     * @param minRateUpdateDelay Minimum time required between redemption rate updates (in seconds)
     * @param maxRateUpdateDelay Maximum time allowed between redemption rate updates (in seconds) before vault automatically pauses
     * @param depositCap Maximum assets that can be deposited (0 means no cap)
     * @param feeDistribution Configuration for fee distribution between platform and strategist
     */
    struct KYCOneWayVaultConfig {
        BaseAccount depositAccount;
        address strategist;
        uint32 depositFeeBps;
        uint32 withdrawFeeBps;
        uint32 maxRateIncrementBps;
        uint32 maxRateDecrementBps;
        uint64 minRateUpdateDelay;
        uint64 maxRateUpdateDelay;
        uint256 depositCap;
        FeeDistributionConfig feeDistribution;
    }

    /**
     * @dev Configuration for fee distribution
     * @param strategistAccount Address to receive strategist's portion of fees
     * @param platformAccount Address to receive platform's portion of fees
     * @param strategistRatioBps Strategist's percentage of total fees in basis points
     */
    struct FeeDistributionConfig {
        address strategistAccount; // Account to receive strategist's portion of fees
        address platformAccount; // Account to receive platform's portion of fees
        uint32 strategistRatioBps; // Strategist's share of total fees in basis points
    }

    /**
     * @dev Vault state information
     * @param paused Whether the vault is currently paused
     * @param pausedByOwner Whether the vault was paused by the owner (affects who can unpause)
     * @param pausedByStaleRate Whether the vault was paused due to a stale redemption rate (affects who can unpause and redemption rate updates)
     */
    struct VaultState {
        bool paused;
        // If paused by owner, only owner can unpause it
        bool pausedByOwner;
        // If vault was paused due to a stale redemption rate
        bool pausedByStaleRate;
    }

    /**
     * @dev Structure for withdrawal requests to destination domain
     * @param id Unique ID for the request
     * @param owner Owner of the request who burned shares
     * @param redemptionRate Redemption rate at time of request
     * @param sharesAmount Amount of shares to be redeemed
     * @param receiver Address to receive assets on destination domain (as string, e.g. Neutron address)
     */
    struct WithdrawRequest {
        uint64 id;
        address owner;
        uint256 redemptionRate;
        uint256 sharesAmount;
        string receiver;
    }

    // Main state variables

    KYCOneWayVaultConfig public config;

    uint256 public redemptionRate;

    VaultState public vaultState;

    uint64 public currentWithdrawRequestId;

    uint64 public lastRateUpdateTimestamp;

    /**
     * @dev Total fees collected but not yet distributed, denominated in asset
     */
    uint256 public feesAccruedInAsset;

    /**
     * @dev Mapping from request ID to withdrawal request details
     */
    mapping(uint64 => WithdrawRequest) public withdrawRequests;

    // Constants

    /**
     * @dev Constant for basis point calculations (100% = 10000)
     */
    uint32 private constant BASIS_POINTS = 1e4;

    uint256 internal ONE_SHARE;

    /**
     * @dev Constructor that disables initializers
     * @notice Required for UUPS proxy pattern
     */
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    /**
     * @dev Initializes the contract replacing the constructor
     * @param _owner Address of the contract owner
     * @param _config Encoded configuration bytes
     * @param underlying Address of the underlying token
     * @param vaultTokenName Name of the vault token
     * @param vaultTokenSymbol Symbol of the vault token
     * @param startingRate Initial redemption rate
     */
    function initialize(
        address _owner,
        bytes memory _config,
        address underlying,
        string memory vaultTokenName,
        string memory vaultTokenSymbol,
        uint256 startingRate
    ) external initializer {
        __ERC20_init(vaultTokenName, vaultTokenSymbol);
        __ERC4626_init(IERC20(underlying));
        __Ownable_init(_owner);
        __ReentrancyGuard_init();
        __UUPSUpgradeable_init();

        config = abi.decode(_config, (KYCOneWayVaultConfig));
        _validateConfig(config);

        ONE_SHARE = 10 ** decimals();
        require(startingRate > 0, "Starting redemption rate cannot be zero");
        redemptionRate = startingRate; // Initialize at specified starting rate
        lastRateUpdateTimestamp = uint64(block.timestamp); // Set initial timestamp for rate updates
    }

    /**
     * @dev Function that authorizes contract upgrades - required by UUPSUpgradeable
     * @param newImplementation address of the new implementation
     */
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {
        // Upgrade logic comes here
        // No additional logic required beyond owner check in modifier
    }

    /**
     * @notice Updates the vault configuration
     * @dev Validates all configuration parameters before updating
     * @param _config Encoded KYCOneWayVaultConfig struct
     */
    function updateConfig(bytes memory _config) external onlyOwner {
        KYCOneWayVaultConfig memory decodedConfig = abi.decode(_config, (KYCOneWayVaultConfig));

        _validateConfig(decodedConfig);

        // All validations passed, update config
        config = decodedConfig;

        emit ConfigUpdated(msg.sender, decodedConfig);
    }

    /**
     * @notice Updates the zkMe configuration
     * @param _zkMe zkMe Verify & Certify contract address
     * @param _cooperator Selector address provided by zkMe
     */
    function updateZkMeConfig(address _zkMe, address _cooperator) external onlyOwner {
        ZkMeConfig storage zkMeConfig = _getZkMeConfig();

        zkMeConfig.zkMe = IZkMe(_zkMe);
        zkMeConfig.cooperator = _cooperator;

        emit ZkMeConfigUpdated(msg.sender, zkMeConfig);
    }

    /**
     * @notice Updates explicit user allowance to use this contract without KYC
     * @param _user Address, which status shall be altered
     * @param _allowed Whether this address is explicitly allowed or not
     */
    function setUserAllowed(address _user, bool _allowed) external onlyOwner {
        if (_allowed) {
            _getKycAllowedUsers().allowedUsers[_user] = true;
        } else {
            delete _getKycAllowedUsers().allowedUsers[_user];
        }

        emit UserAllowed(msg.sender, _user, _allowed);
    }

    /**
     * @notice Returns explicit user allowance to use this contract without KYC
     * @param _user Address, status of which to retrieve
     * @return Whether this address is explicitly allowed or not
     */
    function userAllowed(address _user) external view returns (bool) {
        return _getKycAllowedUsers().allowedUsers[_user];
    }

    /**
     * @dev Validates the configuration parameters
     * @param decodedConfig The decoded KYCOneWayVaultConfig struct
     */
    function _validateConfig(KYCOneWayVaultConfig memory decodedConfig) internal pure {
        if (address(decodedConfig.depositAccount) == address(0)) {
            revert("Deposit account cannot be zero address");
        }

        if (decodedConfig.strategist == address(0)) {
            revert("Strategist cannot be zero address");
        }

        if (decodedConfig.depositFeeBps > BASIS_POINTS) {
            revert("Deposit fee cannot exceed 100%");
        }

        if (decodedConfig.withdrawFeeBps > BASIS_POINTS) {
            revert("Withdraw fee cannot exceed 100%");
        }

        if (decodedConfig.maxRateDecrementBps > BASIS_POINTS) {
            revert("Max rate decrement cannot exceed 100%");
        }

        if (decodedConfig.maxRateUpdateDelay == 0) {
            revert("Max rate update delay cannot be zero");
        }

        if (decodedConfig.minRateUpdateDelay > decodedConfig.maxRateUpdateDelay) {
            revert("Minimum update delay cannot exceed maximum update delay");
        }

        if (decodedConfig.feeDistribution.strategistRatioBps > BASIS_POINTS) {
            revert("Strategist account fee distribution ratio cannot exceed 100%");
        }

        if (decodedConfig.feeDistribution.platformAccount == address(0)) {
            revert("Platform account cannot be zero address");
        }

        if (decodedConfig.feeDistribution.strategistAccount == address(0)) {
            revert("Strategist account cannot be zero address");
        }
    }

    /**
     * @notice Returns the total amount of assets managed by the vault
     * @dev Overrides ERC4626 totalAssets to use current redemption rate
     * @return Total assets calculated from total shares using current redemption rate
     */
    function totalAssets() public view override returns (uint256) {
        return _convertToAssets(totalSupply(), Math.Rounding.Floor);
    }

    /**
     * @notice Returns maximum amount that can be deposited for a receiver
     * @dev Overrides ERC4626 maxDeposit to enforce deposit cap if configured
     * @return Maximum deposit amount allowed
     */
    function maxDeposit(address) public view override returns (uint256) {
        uint256 cap = config.depositCap;
        if (cap == 0) {
            return type(uint256).max;
        }

        uint256 totalDeposits = totalAssets();
        if (totalDeposits >= cap) {
            return 0;
        }

        return cap - totalDeposits;
    }

    /**
     * @notice Returns maximum shares that can be minted for a receiver
     * @dev Overrides ERC4626 maxMint to enforce deposit cap if configured
     * @return Maximum shares that can be minted
     */
    function maxMint(address) public view override returns (uint256) {
        uint256 cap = config.depositCap;
        if (cap == 0) {
            return type(uint256).max;
        }

        uint256 totalDeposits = totalAssets();
        if (totalDeposits >= cap) {
            return 0;
        }

        return _convertToShares(cap - totalDeposits, Math.Rounding.Floor);
    }

    /**
     * @notice Deposits assets into the vault, charging a fee if configured
     * @dev Overrides ERC4626 deposit to handle fees before calling _deposit
     * @param assets Amount of assets to deposit
     * @param receiver Address to receive the vault shares
     * @return shares Amount of shares minted to receiver
     */
    function deposit(uint256 assets, address receiver) public override onlyKyc whenNotPaused nonReentrant returns (uint256) {
        if (_checkAndHandleStaleRate()) {
            return 0; // Exit early if vault was just paused
        }

        uint256 maxAssets = maxDeposit(receiver);
        if (assets > maxAssets) {
            revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets);
        }

        uint256 depositFee = calculateDepositFee(assets);
        uint256 assetsAfterFee = assets - depositFee;

        if (depositFee > 0) {
            feesAccruedInAsset += depositFee;
        }

        uint256 shares = previewDeposit(assetsAfterFee);
        _deposit(msg.sender, receiver, assets, shares);

        return shares;
    }

    /**
     * @notice Mints exact shares to receiver, calculating and charging required assets
     * @dev Overrides ERC4626 mint to handle fees before calling _deposit
     * @param shares Amount of shares to mint
     * @param receiver Address to receive the shares
     * @return assets Total amount of assets deposited (including fees)
     */
    function mint(uint256 shares, address receiver) public override onlyKyc whenNotPaused nonReentrant returns (uint256) {
        if (_checkAndHandleStaleRate()) {
            return 0; // Exit early if vault was just paused
        }
        uint256 maxShares = maxMint(receiver);
        if (shares > maxShares) {
            revert ERC4626ExceededMaxMint(receiver, shares, maxShares);
        }

        (uint256 grossAssets, uint256 fee) = calculateMintFee(shares);

        if (fee > 0) {
            feesAccruedInAsset += fee;
        }

        _deposit(msg.sender, receiver, grossAssets, shares);

        return grossAssets;
    }

    /**
     * @notice Updates the redemption rate and distributes accumulated fees
     * @dev Can only be called by the strategist when the vault is not paused
     * @param newRate New redemption rate to set
     */
    function update(uint256 newRate) external onlyStrategist whenNotManuallyPaused {
        // Validate the new rate
        if (newRate == 0) {
            revert("Redemption rate cannot be zero");
        }

        KYCOneWayVaultConfig memory _config = config;

        // Check that enough time has passed since last update
        if (uint64(block.timestamp) - lastRateUpdateTimestamp < _config.minRateUpdateDelay) {
            revert("Minimum rate update delay not passed");
        }

        // Check that the new rate is within allowed increment/decrement limits
        if (newRate > redemptionRate) {
            // Rate increase
            uint256 maxIncrement = (redemptionRate * config.maxRateIncrementBps) / BASIS_POINTS;
            require(newRate - redemptionRate <= maxIncrement, "Rate increase exceeds maximum allowed increment");
        } else if (newRate < redemptionRate) {
            // Rate decrease
            uint256 maxDecrement = (redemptionRate * config.maxRateDecrementBps) / BASIS_POINTS;
            require(redemptionRate - newRate <= maxDecrement, "Rate decrease exceeds maximum allowed decrement");
        }

        // Distribute accumulated fees
        _distributeFees(_config.feeDistribution);

        // Update state
        redemptionRate = newRate;

        // Update last update timestamp
        lastRateUpdateTimestamp = uint64(block.timestamp);

        emit RateUpdated(newRate);
    }

    /**
     * @notice Pauses the vault, preventing deposits and withdrawals
     * @dev Can be called by owner or strategist, but only owner can unpause if paused by owner
     */
    function pause() external onlyOwnerOrStrategist {
        // Check if vault is already paused
        if (vaultState.paused) {
            revert("Vault is already paused");
        }

        VaultState memory _vaultState;
        if (msg.sender == owner()) {
            _vaultState.pausedByOwner = true;
        } else {
            _vaultState.pausedByOwner = false;
        }

        _vaultState.paused = true;
        vaultState = _vaultState;
        emit PausedStateChanged(true);
    }

    /**
     * @notice Unpauses the vault, allowing deposits and withdrawals
     * @dev If paused by owner or due to a stale rate, only owner can unpause; otherwise either owner or strategist can unpause
     */
    function unpause() external onlyOwnerOrStrategist {
        VaultState memory _vaultState = vaultState;
        // Check if vault is paused by owner or due to stale rate and the sender is the owner
        if (_vaultState.pausedByOwner && msg.sender != owner()) {
            revert("Only owner can unpause if paused by owner");
        }
        if (_vaultState.pausedByStaleRate && msg.sender != owner()) {
            revert("Only owner can unpause if paused by stale rate");
        }
        if (
            _vaultState.pausedByStaleRate
                && uint64(block.timestamp) - lastRateUpdateTimestamp > config.maxRateUpdateDelay
        ) {
            revert("Cannot unpause while rate is stale");
        }

        delete vaultState;
        emit PausedStateChanged(false);
    }

    /**
     * @notice Calculates the gross assets needed and fee for minting shares
     * @param shares Amount of shares to mint
     * @return grossAssets Total assets needed including fee
     * @return fee Fee amount in assets
     */
    function calculateMintFee(uint256 shares) public view returns (uint256, uint256) {
        // Calculate base assets needed for shares
        uint256 baseAssets = previewMint(shares);

        // Calculate gross assets required including fee
        uint256 feeBps = config.depositFeeBps;
        if (feeBps == 0) {
            return (baseAssets, 0);
        }

        // grossAssets = baseAssets / (1 - feeRate)
        // This formula ensures that after the fee is deducted, exactly baseAssets remain
        uint256 grossAssets = baseAssets.mulDiv(BASIS_POINTS, BASIS_POINTS - feeBps, Math.Rounding.Ceil);

        uint256 fee = grossAssets - baseAssets;

        return (grossAssets, fee);
    }

    /**
     * @notice Calculates the fee amount to be charged for a deposit
     * @dev Uses basis points (BPS) for fee calculation where 1 BPS = 0.01%
     *      The fee is rounded up to ensure the protocol doesn't lose dust amounts
     *      If the deposit fee BPS is set to 0, returns 0 to optimize gas
     * @param assets The amount of assets being deposited
     * @return The fee amount in the same decimals as the asset
     */
    function calculateDepositFee(uint256 assets) public view returns (uint256) {
        uint32 feeBps = config.depositFeeBps;
        if (feeBps == 0) return 0;

        uint256 fee = assets.mulDiv(feeBps, BASIS_POINTS, Math.Rounding.Ceil);

        return fee;
    }

    /**
     * @notice Calculates the withdrawal fee for a given amount of assets
     * @dev Uses basis points (BPS) for fee calculation where 1 BPS = 0.01%
     *      The fee is rounded up to ensure the protocol doesn't lose dust amounts
     *      If the withdraw rate BPS is set to 0, returns 0 to optimize gas
     * @param assets The amount of assets being withdrawn
     * @return The withdrawal fee amount in the same decimals as the asset
     */
    function calculateWithdrawalFee(uint256 assets) public view returns (uint256) {
        uint32 feeBps = config.withdrawFeeBps;
        if (feeBps == 0) return 0;

        uint256 fee = assets.mulDiv(feeBps, BASIS_POINTS, Math.Rounding.Ceil);

        return fee;
    }

    /**
     * @notice Creates a withdrawal request for assets to be processed on destination domain
     * @dev Assets are calculated from shares based on current redemption rate
     * @param assets Amount of assets to withdraw (gross amount including fee)
     * @param receiver Address to receive the withdrawn assets on the destination domain (as string)
     * @param owner Address that owns the shares
     */
    function withdraw(uint256 assets, string calldata receiver, address owner) external nonReentrant whenNotPaused {
        if (_checkAndHandleStaleRate()) {
            return; // Exit early if vault was just paused
        }
        _validateWithdrawParams(owner, receiver, assets);

        // Check if assets exceed max withdraw amount
        uint256 maxAssets = maxWithdraw(owner);
        if (assets > maxAssets) {
            revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets);
        }

        // Calculate withdrawal fee
        uint256 withdrawalFee = calculateWithdrawalFee(assets);

        // Calculate net assets after fee
        uint256 netAssets = assets - withdrawalFee;

        // Calculate shares to burn based on FULL asset amount (including fee)
        uint256 sharesToBurn = previewWithdraw(assets);

        // Calculate shares for request based on NET assets (what will be processed)
        uint256 postFeeShares = previewWithdraw(netAssets);

        // Track fee for later distribution
        if (withdrawalFee > 0) {
            feesAccruedInAsset += withdrawalFee;
        }

        _withdraw(sharesToBurn, postFeeShares, receiver, owner);
    }

    /**
     * @notice Creates a redemption request for shares to be processed on destination domain
     * @param shares Amount of shares to redeem (gross amount)
     * @param receiver Address to receive the redeemed assets on destination domain (as string)
     * @param owner Address that owns the shares
     */
    function redeem(uint256 shares, string calldata receiver, address owner) external nonReentrant whenNotPaused {
        if (_checkAndHandleStaleRate()) {
            return; // Exit early if vault was just paused
        }

        _validateWithdrawParams(owner, receiver, shares);

        // Check if shares exceed max redeem amount
        uint256 maxShares = maxRedeem(owner);
        if (shares > maxShares) {
            revert ERC4626ExceededMaxRedeem(owner, shares, maxShares);
        }

        // Calculate gross assets from shares
        uint256 grossAssets = _convertToAssets(shares, Math.Rounding.Floor);

        // Calculate withdrawal fee
        uint256 withdrawalFee = calculateWithdrawalFee(grossAssets);

        // Calculate net assets after fee
        uint256 netAssets = grossAssets - withdrawalFee;

        // Calculate shares for request based on net assets (what will be processed)
        uint256 postFeeShares = _convertToShares(netAssets, Math.Rounding.Ceil);

        // Track fee for later distribution
        if (withdrawalFee > 0) {
            feesAccruedInAsset += withdrawalFee;
        }

        // Burn the full shares amount specified by user, store net shares in request
        _withdraw(shares, postFeeShares, receiver, owner);
    }

    /**
     * @dev Internal function to handle withdrawal/redemption request creation
     * @param sharesToBurn Amount of shares to burn from user's balance
     * @param postFeeShares Amount of shares to store in withdrawal request (net after fees)
     * @param receiver Address to receive the assets on the destination domain (as string)
     * @param owner Address that owns the shares
     */
    function _withdraw(uint256 sharesToBurn, uint256 postFeeShares, string calldata receiver, address owner) internal {
        // Burn shares first (CEI pattern - Checks, Effects, Interactions)
        if (msg.sender != owner) {
            _spendAllowance(owner, msg.sender, sharesToBurn);
        }
        _burn(owner, sharesToBurn);

        WithdrawRequest memory request = WithdrawRequest({
            id: currentWithdrawRequestId,
            owner: owner,
            sharesAmount: postFeeShares, // Net shares that will be processed
            redemptionRate: redemptionRate,
            receiver: receiver
        });

        // Store the request
        withdrawRequests[currentWithdrawRequestId] = request;

        // Emit the event with the shares post fee that are being withdrawn
        emit WithdrawRequested(currentWithdrawRequestId, owner, receiver, postFeeShares);

        // Increment the request ID for the next request
        currentWithdrawRequestId++;
    }

    /**
     * @dev Internal function to handle deposit implementation
     * @param caller Address initiating the deposit
     * @param receiver Address to receive the minted shares
     * @param assets Amount of assets being deposited
     * @param shares Amount of shares to mint
     */
    function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override {
        // Transfer assets to the deposit account (external contract)
        SafeERC20.safeTransferFrom(IERC20(asset()), caller, address(config.depositAccount), assets);
        _mint(receiver, shares);

        emit Deposit(caller, receiver, assets, shares);
    }

    /**
     * @dev Converts shares to assets using current redemption rate
     * @param shares Amount of shares to convert
     * @param rounding Rounding direction (up or down)
     * @return Equivalent amount of assets
     */
    function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view override returns (uint256) {
        return shares.mulDiv(redemptionRate, ONE_SHARE, rounding);
    }

    /**
     * @dev Converts assets to shares using current redemption rate
     * @param assets Amount of assets to convert
     * @param rounding Rounding direction (up or down)
     * @return Equivalent amount of shares
     */
    function _convertToShares(uint256 assets, Math.Rounding rounding) internal view override returns (uint256) {
        return assets.mulDiv(ONE_SHARE, redemptionRate, rounding);
    }

    /**
     * @dev Distributes accumulated fees between strategist and platform
     * @param feeDistribution Fee distribution configuration
     */
    function _distributeFees(FeeDistributionConfig memory feeDistribution) internal {
        uint256 _feesAccruedInAsset = feesAccruedInAsset;
        if (_feesAccruedInAsset == 0) return;

        // Calculate fee shares for strategist
        uint256 strategistAssets =
            _feesAccruedInAsset.mulDiv(feeDistribution.strategistRatioBps, BASIS_POINTS, Math.Rounding.Floor);

        // Calculate platform's share as the remainder
        uint256 platformAssets = _feesAccruedInAsset - strategistAssets;

        // Convert assets to shares
        uint256 strategistShares = _convertToShares(strategistAssets, Math.Rounding.Floor);
        uint256 platformShares = _convertToShares(platformAssets, Math.Rounding.Floor);

        // Reset fees accrued
        feesAccruedInAsset = 0;

        // Mint shares to respective accounts
        if (strategistShares > 0) {
            _mint(feeDistribution.strategistAccount, strategistShares);
        }
        if (platformShares > 0) {
            _mint(feeDistribution.platformAccount, platformShares);
        }

        emit FeesDistributed(
            feeDistribution.strategistAccount, feeDistribution.platformAccount, strategistShares, platformShares
        );
    }

    /**
     * @dev Internal function to validate common withdraw/redeem parameters
     * @param owner Address that owns the shares
     * @param receiver Address to receive the assets on the destination domain (as string)
     * @param amount Amount of shares/assets to withdraw
     */
    function _validateWithdrawParams(address owner, string calldata receiver, uint256 amount) internal pure {
        if (owner == address(0)) revert("Owner of shares cannot be zero address");
        if (bytes(receiver).length == 0) revert("Receiver cannot be empty");
        if (amount == 0) revert("Amount to withdraw cannot be zero");
    }

    /**
     * @dev Internal function that will pause the vault if the redemption rate has been stale for too long
     * @return True if the vault was paused due to stale rate, false otherwise
     */
    function _checkAndHandleStaleRate() internal returns (bool) {
        // Check if the last update was too long ago
        if (uint64(block.timestamp) - lastRateUpdateTimestamp > config.maxRateUpdateDelay) {
            // Pause the vault due to stale rate
            vaultState.paused = true;
            vaultState.pausedByStaleRate = true;
            emit PausedStateChanged(true);
            emit StaleRatePaused(lastRateUpdateTimestamp, uint64(block.timestamp));
            return true;
        }
        return false;
    }

    /**
     * @notice Fallback function that reverts all calls to non-existent functions
     * @dev Called when no other function matches the function signature
     */
    fallback() external {
        revert("Function not found");
    }
}
"
    },
    "dependencies/@openzeppelin-contracts-upgradeable-5.2.0/token/ERC20/extensions/ERC4626Upgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC4626.sol)

pragma solidity ^0.8.20;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {ERC20Upgradeable} from "../ERC20Upgradeable.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Initializable} from "../../../proxy/utils/Initializable.sol";

/**
 * @dev Implementation of the ERC-4626 "Tokenized Vault Standard" as defined in
 * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
 *
 * This extension allows the minting and burning of "shares" (represented using the ERC-20 inheritance) in exchange for
 * underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends
 * the ERC-20 standard. Any additional extensions included along it would affect the "shares" token represented by this
 * contract and not the "assets" token which is an independent contract.
 *
 * [CAUTION]
 * ====
 * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning
 * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation
 * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial
 * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may
 * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by
 * verifying the amount received is as expected, using a wrapper that performs these checks such as
 * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router].
 *
 * Since v4.9, this implementation introduces configurable virtual assets and shares to help developers mitigate that risk.
 * The `_decimalsOffset()` corresponds to an offset in the decimal representation between the underlying asset's decimals
 * and the vault decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which
 * itself determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default
 * offset (0) makes it non-profitable even if an attacker is able to capture value from multiple user deposits, as a result
 * of the value being captured by the virtual shares (out of the attacker's donation) matching the attacker's expected gains.
 * With a larger offset, the attack becomes orders of magnitude more expensive than it is profitable. More details about the
 * underlying math can be found xref:erc4626.adoc#inflation-attack[here].
 *
 * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued
 * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets
 * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience
 * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the
 * `_convertToShares` and `_convertToAssets` functions.
 *
 * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide].
 * ====
 */
abstract contract ERC4626Upgradeable is Initializable, ERC20Upgradeable, IERC4626 {
    using Math for uint256;

    /// @custom:storage-location erc7201:openzeppelin.storage.ERC4626
    struct ERC4626Storage {
        IERC20 _asset;
        uint8 _underlyingDecimals;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC4626")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant ERC4626StorageLocation = 0x0773e532dfede91f04b12a73d3d2acd361424f41f76b4fb79f090161e36b4e00;

    function _getERC4626Storage() private pure returns (ERC4626Storage storage $) {
        assembly {
            $.slot := ERC4626StorageLocation
        }
    }

    /**
     * @dev Attempted to deposit more assets than the max amount for `receiver`.
     */
    error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max);

    /**
     * @dev Attempted to mint more shares than the max amount for `receiver`.
     */
    error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max);

    /**
     * @dev Attempted to withdraw more assets than the max amount for `receiver`.
     */
    error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max);

    /**
     * @dev Attempted to redeem more shares than the max amount for `receiver`.
     */
    error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max);

    /**
     * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC-20 or ERC-777).
     */
    function __ERC4626_init(IERC20 asset_) internal onlyInitializing {
        __ERC4626_init_unchained(asset_);
    }

    function __ERC4626_init_unchained(IERC20 asset_) internal onlyInitializing {
        ERC4626Storage storage $ = _getERC4626Storage();
        (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_);
        $._underlyingDecimals = success ? assetDecimals : 18;
        $._asset = asset_;
    }

    /**
     * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way.
     */
    function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool ok, uint8 assetDecimals) {
        (bool success, bytes memory encodedDecimals) = address(asset_).staticcall(
            abi.encodeCall(IERC20Metadata.decimals, ())
        );
        if (success && encodedDecimals.length >= 32) {
            uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256));
            if (returnedDecimals <= type(uint8).max) {
                return (true, uint8(returnedDecimals));
            }
        }
        return (false, 0);
    }

    /**
     * @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This
     * "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the
     * asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals.
     *
     * See {IERC20Metadata-decimals}.
     */
    function decimals() public view virtual override(IERC20Metadata, ERC20Upgradeable) returns (uint8) {
        ERC4626Storage storage $ = _getERC4626Storage();
        return $._underlyingDecimals + _decimalsOffset();
    }

    /** @dev See {IERC4626-asset}. */
    function asset() public view virtual returns (address) {
        ERC4626Storage storage $ = _getERC4626Storage();
        return address($._asset);
    }

    /** @dev See {IERC4626-totalAssets}. */
    function totalAssets() public view virtual returns (uint256) {
        ERC4626Storage storage $ = _getERC4626Storage();
        return $._asset.balanceOf(address(this));
    }

    /** @dev See {IERC4626-convertToShares}. */
    function convertToShares(uint256 assets) public view virtual returns (uint256) {
        return _convertToShares(assets, Math.Rounding.Floor);
    }

    /** @dev See {IERC4626-convertToAssets}. */
    function convertToAssets(uint256 shares) public view virtual returns (uint256) {
        return _convertToAssets(shares, Math.Rounding.Floor);
    }

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

    /** @dev See {IERC4626-maxMint}. */
    function maxMint(address) public view virtual returns (uint256) {
        return type(uint256).max;
    }

    /** @dev See {IERC4626-maxWithdraw}. */
    function maxWithdraw(address owner) public view virtual returns (uint256) {
        return _convertToAssets(balanceOf(owner), Math.Rounding.Floor);
    }

    /** @dev See {IERC4626-maxRedeem}. */
    function maxRedeem(address owner) public view virtual returns (uint256) {
        return balanceOf(owner);
    }

    /** @dev See {IERC4626-previewDeposit}. */
    function previewDeposit(uint256 assets) public view virtual returns (uint256) {
        return _convertToShares(assets, Math.Rounding.Floor);
    }

    /** @dev See {IERC4626-previewMint}. */
    function previewMint(uint256 shares) public view virtual returns (uint256) {
        return _convertToAssets(shares, Math.Rounding.Ceil);
    }

    /** @dev See {IERC4626-previewWithdraw}. */
    function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
        return _convertToShares(assets, Math.Rounding.Ceil);
    }

    /** @dev See {IERC4626-previewRedeem}. */
    function previewRedeem(uint256 shares) public view virtual returns (uint256) {
        return _convertToAssets(shares, Math.Rounding.Floor);
    }

    /** @dev See {IERC4626-deposit}. */
    function deposit(uint256 assets, address receiver) public virtual returns (uint256) {
        uint256 maxAssets = maxDeposit(receiver);
        if (assets > maxAssets) {
            revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets);
        }

        uint256 shares = previewDeposit(assets);
        _deposit(_msgSender(), receiver, assets, shares);

        return shares;
    }

    /** @dev See {IERC4626-mint}. */
    function mint(uint256 shares, address receiver) public virtual returns (uint256) {
        uint256 maxShares = maxMint(receiver);
        if (shares > maxShares) {
            revert ERC4626ExceededMaxMint(receiver, shares, maxShares);
        }

        uint256 assets = previewMint(shares);
        _deposit(_msgSender(), receiver, assets, shares);

        return assets;
    }

    /** @dev See {IERC4626-withdraw}. */
    function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256) {
        uint256 maxAssets = maxWithdraw(owner);
        if (assets > maxAssets) {
            revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets);
        }

        uint256 shares = previewWithdraw(assets);
        _withdraw(_msgSender(), receiver, owner, assets, shares);

        return shares;
    }

    /** @dev See {IERC4626-redeem}. */
    function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256) {
        uint256 maxShares = maxRedeem(owner);
        if (shares > maxShares) {
            revert ERC4626ExceededMaxRedeem(owner, shares, maxShares);
        }

        uint256 assets = previewRedeem(shares);
        _withdraw(_msgSender(), receiver, owner, assets, shares);

        return assets;
    }

    /**
     * @dev Internal conversion function (from assets to shares) with support for rounding direction.
     */
    function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) {
        return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding);
    }

    /**
     * @dev Internal conversion function (from shares to assets) with support for rounding direction.
     */
    function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) {
        return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding);
    }

    /**
     * @dev Deposit/mint common workflow.
     */
    function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual {
        ERC4626Storage storage $ = _getERC4626Storage();
        // If _asset is ERC-777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the
        // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer,
        // calls the vault, which is assumed not malicious.
        //
        // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the
        // assets are transferred and before the shares are minted, which is a valid state.
        // slither-disable-next-line reentrancy-no-eth
        SafeERC20.safeTransferFrom($._asset, caller, address(this), assets);
        _mint(receiver, shares);

        emit Deposit(caller, receiver, assets, shares);
    }

    /**
     * @dev Withdraw/redeem common workflow.
     */
    function _withdraw(
        address caller,
        address receiver,
        address owner,
        uint256 assets,
        uint256 shares
    ) internal virtual {
        ERC4626Storage storage $ = _getERC4626Storage();
        if (caller != owner) {
            _spendAllowance(owner, caller, shares);
        }

        // If _asset is ERC-777, `transfer` can trigger a reentrancy AFTER the transfer happens through the
        // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer,
        // calls the vault, which is assumed not malicious.
        //
        // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the
        // shares are burned and after the assets are transferred, which is a valid state.
        _burn(owner, shares);
        SafeERC20.safeTransfer($._asset, receiver, assets);

        emit Withdraw(caller, receiver, owner, assets, shares);
    }

    function _decimalsOffset() internal view virtual returns (uint8) {
        return 0;
    }
}
"
    },
    "dependencies/@openzeppelin-contracts-upgradeable-5.2.0/utils/ReentrancyGuardUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuardUpgradeable is Initializable {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    /// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard
    struct ReentrancyGuardStorage {
        uint256 _status;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;

    function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {
        assembly {
            $.slot := ReentrancyGuardStorageLocation
        }
    }

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    function __ReentrancyGuard_init() internal onlyInitializing {
        __ReentrancyGuard_init_unchained();
    }

    function __ReentrancyGuard_init_unchained() internal onlyInitializing {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        $._status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if ($._status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        $._status = ENTERED;
    }

    function _nonReentrantAfter() private {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        $._status = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        return $._status == ENTERED;
    }
}
"
    },
    "src/BaseAccount.sol": {
      "content": "// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.28;

import {Account} from "./Account.sol";

/**
 * @title BaseAccount
 * @dev Basic implementation of Account contract with no additional functionality
 */
contract BaseAccount is Account {
    constructor(address _owner, address[] memory _libraries) Account(_owner, _libraries) {}
}
"
    },
    "dependencies/@openzeppelin-contracts-upgradeable-5.2.0/access/OwnableUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
    /// @custom:storage-location erc7201:openzeppelin.storage.Ownable
    struct OwnableStorage {
        address _owner;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;

    function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
        assembly {
            $.slot := OwnableStorageLocation
        }
    }

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    function __Ownable_init(address initialOwner) internal onlyInitializing {
        __Ownable_init_unchained(initialOwner);
    }

    function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        OwnableStorage storage $ = _getOwnableStorage();
        return $._owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        OwnableStorage storage $ = _getOwnableStorage();
        address oldOwner = $._owner;
        $._owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
"
    },
    "dependencies/@openzeppelin-contracts-upgradeable-5.2.0/proxy/utils/UUPSUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/utils/UUPSUpgradeable.sol)

pragma solidity ^0.8.22;

import {IERC1822Proxiable} from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {Initializable} from "./Initializable.sol";

/**
 * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
 * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
 *
 * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
 * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
 * `UUPSUpgradeable` with a custom implementation of upgrades.
 *
 * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
 */
abstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable {
    /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
    address private immutable __self = address(this);

    /**
     * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
     * and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
     * while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
     * If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
     * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
     * during an upgrade.
     */
    string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";

    /**
     * @dev The call is from an unauthorized context.
     */
    error UUPSUnauthorizedCallContext();

    /**
     * @dev The storage `slot` is unsupported as a UUID.
     */
    error UUPSUnsupportedProxiableUUID(bytes32 slot);

    /**
     * @dev Check that the execution is being performed through a delegatecall call and that the execution context is
     * a proxy contract with an implementation (as defined in ERC-1967) pointing to self. This should only be the case
     * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
     * function through ERC-1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
     * fail.
     */
    modifier onlyProxy() {
        _checkProxy();
        _;
    }

    /**
     * @dev Check that the execution is not being performed through a delegate call. This allows a function to be
     * callable on the implementing contract but not through proxies.
     */
    modifier notDelegated() {
        _checkNotDelegated();
        _;
    }

    function __UUPSUpgradeable_init() internal onlyInitializing {
    }

    function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the
     * implementation. It is used to validate the implementation's compatibility when performing an upgrade.
     *
     * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
     * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
     * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
     */
    function proxiableUUID() external view virtual notDelegated returns (bytes32) {
        return ERC1967Utils.IMPLEMENTATION_SLOT;
    }

    /**
     * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
     * encoded in `data`.
     *
     * Calls {_authorizeUpgrade}.
     *
     * Emits an {Upgraded} event.
     *
     * @custom:oz-upgrades-unsafe-allow-reachable delegatecall
     */
    function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallUUPS(newImplementation, data);
    }

    /**
     * @dev Reverts if the execution is not performed via delegatecall or the execution
     * context is not of a proxy with an ERC-1967 compliant implementation pointing to self.
     * See {_onlyProxy}.
     */
    function _checkProxy() internal view virtual {
        if (
            address(this) == __self || // Must be called through delegatecall
            ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
        ) {
            revert UUPSUnauthorizedCallContext();
        }
    }

    /**
     * @dev Reverts if the execution is performed via delegatecall.
     * See {notDelegated}.
     */
    function _checkNotDelegated() internal view virtual {
        if (address(this) != __self) {
            // Must not be called through delegatecall
            revert UUPSUnauthorizedCallContext();
        }
    }

    /**
     * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
     * {upg

Tags:
ERC20, ERC165, Multisig, Mintable, Pausable, Yield, Upgradeable, Multi-Signature, Factory|addr:0x269e8d862c5375c475ef14b66c0c5e1f682a1585|verified:true|block:23384474|tx:0x34c2ebdd673b3a1f6907696b08751e17825db4aa9daed38b839ab618f39e68db|first_check:1758134437

Submitted on: 2025-09-17 20:40:38

Comments

Log in to comment.

No comments yet.