WithdrawModule

Description:

Smart contract deployed on Ethereum with Factory features.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts/interfaces/modules/IWithdrawModule.sol": {
      "content": "pragma solidity 0.8.21;\r
\r
interface IWithdrawModule {\r
    function request(\r
        address caller,\r
        address receiver,\r
        address owner,\r
        uint256 shares\r
    ) external;\r
\r
    function claim(address receiver, uint256 shares) external;\r
\r
    function claimable(\r
        address receiver\r
    ) external view returns (uint256, uint256);\r
\r
    function settle(uint256 shares, uint256 rate) external;\r
}\r
"
    },
    "contracts/modules/WithdrawModule.sol": {
      "content": "pragma solidity 0.8.21;\r
import {IWithdrawModule} from "../interfaces/modules/IWithdrawModule.sol";\r
\r
/// @title Batched withdrawal request/settlement module\r
/// @notice Queues per-user share withdrawal requests into epochs and settles them pro-rata at an assets-per-share rate.\r
/// @dev\r
/// - All state-changing functions are restricted to the configured Hook via onlyHook.\r
/// - Epochs are indexed by epochId. New requests are tagged with the current epochId and are settled\r
///   when the Hook calls settle(). After settlement, epochId is incremented to open a new epoch.\r
/// - Fulfillment is stored in basis points (BPS) and the settlement rate is stored as assets-per-share\r
///   scaled by 10**shareDecimal.\r
/// - This module does not move tokens. The Hook is responsible for redeeming shares, transferring assets,\r
///   and invoking claim() to decrement user requests accordingly.\r
/// @custom:security Only the Hook may call mutating functions. Use a trusted Hook.\r
contract WithdrawModule is IWithdrawModule {\r
    /// @notice Per-user withdrawal request state.\r
    struct Order {\r
        /// @notice Total shares requested for withdrawal that are still outstanding.\r
        uint256 shares;\r
        /// @notice The epoch in which the latest request was made.\r
        uint256 epochId;\r
    }\r
\r
    /// @notice Settlement data for an epoch.\r
    struct EpochInfo {\r
        /// @notice Fulfillment as BPS of requested shares settled in this epoch (10_000 = 100%).\r
        uint256 fulfillment;\r
        /// @notice Assets-per-share settlement rate scaled by 10**shareDecimal.\r
        uint256 rate;\r
    }\r
\r
    /// @notice Current open epoch identifier (first epoch is 1).\r
    uint256 public epochId = 1;\r
\r
    /// @notice Total shares requested for withdrawal across all users for the current open epoch.\r
    uint256 public totalWithdraw;\r
\r
    /// @notice Basis points denominator (10_000 = 100%).\r
    uint256 constant ONE_HUNDRED_PERCENT = 10000;\r
\r
    /// @notice Decimals used to scale the assets-per-share rate (typically vault share decimals).\r
    uint256 immutable shareDecimal;\r
\r
    /// @notice Hook authorized to call this module.\r
    address immutable hook;\r
\r
    /// @notice Pending withdrawal orders by receiver.\r
    mapping(address => Order) public orders;\r
\r
    /// @notice Settlement info per epoch.\r
    mapping(uint256 => EpochInfo) public epochs;\r
\r
    /// @notice Initialize the withdraw module.\r
    /// @param _shareDecimal Decimals used to scale assets-per-share rate (matches the share token decimals).\r
    /// @param _hook Address of the Hook that controls this module.\r
    constructor(uint256 _shareDecimal, address _hook) {\r
        shareDecimal = _shareDecimal;\r
        hook = _hook;\r
    }\r
\r
    /// @notice Restricts function access to the configured Hook.\r
    modifier onlyHook() {\r
        require(msg.sender == hook, "Not hook");\r
        _;\r
    }\r
\r
    /// @notice Emitted when a receiver requests a withdrawal.\r
    /// @param receiver Address for which the withdrawal was requested.\r
    /// @param shares Amount of shares requested.\r
    event WithdrawRequested(address indexed receiver, uint256 shares);\r
\r
    /// @notice Emitted when an epoch is settled and a new epoch is opened.\r
    /// @param epochId The newly opened epoch id (the previous epochId - 1 was just settled).\r
    /// @param rate Assets-per-share settlement rate for the settled epoch, scaled by 10**shareDecimal.\r
    event EpochSettled(uint256 indexed epochId, uint256 rate);\r
\r
    /// @notice Emitted when a receiver's settled shares are claimed (accounting-only in this module).\r
    /// @param receiver Address claiming settled shares.\r
    /// @param shares Amount of shares accounted as claimed.\r
    event WithdrawClaimed(address indexed receiver, uint256 shares);\r
\r
    /// @notice Queue a withdrawal request for shares in the current epoch.\r
    /// @param caller Original msg.sender initiating the withdrawal (informational).\r
    /// @param receiver Address that will receive assets when claimed.\r
    /// @param owner Share owner authorizing the withdrawal (informational).\r
    /// @param shares Amount of shares to request.\r
    /// @dev\r
    /// - Accumulates requested shares in orders[receiver].\r
    /// - Tags the order with the current epochId if it lags behind.\r
    /// - Increments totalWithdraw for the current epoch.\r
    /// - Token movements are handled by the Hook; this function updates accounting only.\r
    function request(\r
        address caller,\r
        address receiver,\r
        address owner,\r
        uint256 shares\r
    ) external onlyHook {\r
        // Custom logic to handle withdraw requests\r
        orders[receiver].shares += shares;\r
        if (orders[receiver].epochId < epochId) {\r
            orders[receiver].epochId = epochId;\r
        }\r
        totalWithdraw += shares;\r
\r
        emit WithdrawRequested(receiver, shares);\r
    }\r
\r
    /// @notice Account for a receiver's claim of settled shares.\r
    /// @param receiver Address claiming.\r
    /// @param shares Number of shares being marked as claimed.\r
    /// @dev\r
    /// - Reverts if attempting to claim more shares than requested.\r
    /// - When a receiver's outstanding shares reach zero, clears their epochId.\r
    /// - Asset transfer is performed by the Hook after computing claimable().\r
    function claim(address receiver, uint256 shares) external onlyHook {\r
        // Custom logic to process claims for the receiver\r
        require(orders[receiver].shares >= shares, "Exceed requested shares");\r
        orders[receiver].shares -= shares;\r
        if (orders[receiver].shares == 0) {\r
            orders[receiver].epochId = 0;\r
        }\r
\r
        emit WithdrawClaimed(receiver, shares);\r
    }\r
\r
    /// @notice Settle a portion of pending withdrawals for the current epoch with a given rate.\r
    /// @param shares Total shares being settled in this epoch across all users.\r
    /// @param rate Assets-per-share settlement rate scaled by 10**shareDecimal.\r
    /// @dev\r
    /// - Records fulfillment = shares / totalWithdraw (in BPS) for the current epoch.\r
    /// - Stores the provided settlement rate.\r
    /// - Reduces totalWithdraw by shares and increments epochId (opening a new epoch).\r
    /// - Emits EpochSettled with the newly opened epoch id.\r
    function settle(uint256 shares, uint256 rate) external onlyHook {\r
        // Custom logic to settle withdraw requests at the end of an epoch\r
        require(shares <= totalWithdraw, "Exceed total withdraw");\r
        uint256 fulfillment = (shares * ONE_HUNDRED_PERCENT) / totalWithdraw;\r
        epochs[epochId] = EpochInfo(fulfillment, rate);\r
        totalWithdraw -= shares;\r
        epochId++;\r
\r
        emit EpochSettled(epochId, rate);\r
    }\r
\r
    /// @notice Compute claimable assets and shares for a receiver across all settled epochs.\r
    /// @param receiver Beneficiary address to query.\r
    /// @return assets Total claimable assets for the receiver.\r
    /// @return shares Total shares that will be marked as claimed.\r
    /// @dev\r
    /// - If the receiver last requested in the current open epoch, nothing is claimable yet.\r
    /// - Iterates from the receiver's last request epoch up to the most recently settled epoch\r
    ///   applying each epoch's fulfillment and rate.\r
    /// - Assets are computed as settledShares * rate / 10**shareDecimal.\r
    function claimable(\r
        address receiver\r
    ) external view returns (uint256 assets, uint256 shares) {\r
        // Custom logic to calculate claimable assets and shares for the receiver\r
        if (orders[receiver].epochId == epochId) {\r
            return (0, 0);\r
        }\r
        uint256 lastRequestId = orders[receiver].epochId;\r
\r
        uint256 totalAssets;\r
        uint256 remainShares = orders[receiver].shares;\r
\r
        while (lastRequestId < epochId) {\r
            uint256 settledShares = (remainShares *\r
                epochs[lastRequestId].fulfillment) / ONE_HUNDRED_PERCENT;\r
            uint256 settledAssets = (settledShares *\r
                epochs[lastRequestId].rate) / 10 ** shareDecimal;\r
            totalAssets += settledAssets;\r
            remainShares -= settledShares;\r
            lastRequestId++;\r
        }\r
\r
        return (totalAssets, orders[receiver].shares - remainShares);\r
    }\r
}\r
"
    }
  },
  "settings": {
    "evmVersion": "paris",
    "optimizer": {
      "enabled": false,
      "runs": 200
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "metadata": {
      "useLiteralContent": true
    }
  }
}}

Tags:
Factory|addr:0x05ddcaa6ce8584f4fa0582e266d83fa85f3fb90b|verified:true|block:23654643|tx:0x0d3c56f28a64031d61e49d7b4d21d3624cfc867769e577e0fa1da2f09374d930|first_check:1761401220

Submitted on: 2025-10-25 16:07:01

Comments

Log in to comment.

No comments yet.