WithdrawQueue

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/WithdrawQueue.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.28;

import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";
import {IERC20Metadata as IERC20} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

import {WrappedNLP} from "./WrappedNLP.sol";
import {SafeERC20} from "./libraries/SafeERC20.sol";
import {ErrorsLib} from "./libraries/ErrorsLib.sol";
import {ReentrancyGuardTransient} from "./libraries/ReentrancyGuardTransient.sol";

/**
 * @title WithdrawQueue - Queue-based withdrawal system for WrappedNLP
 * @dev Manages withdrawal requests with time windows to prevent JIT attacks
 */
contract WithdrawQueue is Ownable2Step, Pausable, ReentrancyGuardTransient {
    using SafeERC20 for IERC20;

    struct WithdrawalRequest {
        uint256 wNLPAmount; // Amount of wNLP tokens to withdraw
        uint256 claimableTime; // When the request becomes claimable
    }

    /*//////////////////////////////////////////////////////////////////////////
                                    STATE VARIABLES
    //////////////////////////////////////////////////////////////////////////*/

    /// @notice Name of the queue
    string public name;

    /// @notice The WrappedNLP contract
    WrappedNLP public immutable wrappedNLP;

    /// @notice Minimum withdrawal amount
    uint256 public constant MIN_WITHDRAWAL_AMOUNT = 1000;

    /// @notice Maximum withdrawal window that can be set
    uint256 public constant MAX_WITHDRAWAL_WINDOW = 14 days;

    /// @notice Default withdrawal window in seconds
    uint256 public withdrawalWindow = 8 hours;

    /// @notice Total unclaimed wNLP amount in the queue
    uint256 public totalUnclaimedAmount;

    /// @notice Mapping from user address to their withdrawal request
    mapping(address => WithdrawalRequest) public withdrawalRequests;

    /*//////////////////////////////////////////////////////////////////////////
                                    EVENTS
    //////////////////////////////////////////////////////////////////////////*/

    /// @notice Emitted when a withdrawal request is created
    event WithdrawalRequested(address indexed user, uint256 wNLPAmount, uint256 claimableTime);

    /// @notice Emitted when a withdrawal is claimed
    event WithdrawalClaimed(address indexed user, address indexed to, uint256 wNLPAmount, uint256 underlyingAmount);

    /// @notice Emitted when a withdrawal request is cancelled
    event WithdrawalCancelled(address indexed user, uint256 wNLPAmount);

    /// @notice Emitted when withdrawal window is updated
    event WithdrawalWindowUpdated(uint256 oldWindow, uint256 newWindow);

    constructor(WrappedNLP _wrappedNLP) {
        wrappedNLP = _wrappedNLP;
        name = string.concat(_wrappedNLP.symbol(), "-Queue");
    }

    /// @notice Request a withdrawal of wNLP tokens
    /// @param wNLPAmount Amount of wNLP tokens to withdraw
    function requestWithdrawal(uint256 wNLPAmount) external nonReentrant whenNotPaused {
        require(wNLPAmount >= MIN_WITHDRAWAL_AMOUNT, "Below minimum withdrawal amount");
        require(withdrawalRequests[msg.sender].wNLPAmount == 0, "Pending request exists");

        // Transfer wNLP tokens for escrow
        IERC20(wrappedNLP).safeTransferFrom(msg.sender, address(this), wNLPAmount);

        uint256 claimableTime = block.timestamp + withdrawalWindow;

        withdrawalRequests[msg.sender] = WithdrawalRequest({wNLPAmount: wNLPAmount, claimableTime: claimableTime});

        // Update total unclaimed amount
        totalUnclaimedAmount += wNLPAmount;

        emit WithdrawalRequested(msg.sender, wNLPAmount, claimableTime);
    }

    /// @notice Claim a withdrawal request to the caller's address
    /// @return underlyingAmount Amount of underlying tokens received
    function claimWithdrawal() external nonReentrant returns (uint256 underlyingAmount) {
        return _claimWithdrawal(msg.sender, msg.sender);
    }

    /// @notice Claim a withdrawal request to a specified address
    /// @param to Address to receive the underlying tokens
    /// @return underlyingAmount Amount of underlying tokens received
    function claimWithdrawalTo(address to) external nonReentrant returns (uint256 underlyingAmount) {
        require(to != address(0), ErrorsLib.ZeroAddress());
        return _claimWithdrawal(msg.sender, to);
    }

    /// @notice Cancel a withdrawal request and return the wNLP tokens to the caller
    /// @dev Users can cancel at any time (even after claimableTime) to maintain flexibility and user autonomy
    /// @return wNLPAmount Amount of wNLP tokens returned to the user
    function cancelWithdrawal() external nonReentrant returns (uint256 wNLPAmount) {
        WithdrawalRequest storage request = withdrawalRequests[msg.sender];

        require(request.wNLPAmount > 0, "No pending request");

        wNLPAmount = request.wNLPAmount;

        delete withdrawalRequests[msg.sender];

        // Update total unclaimed amount
        totalUnclaimedAmount -= wNLPAmount;

        // Return wNLP tokens to the user
        IERC20(wrappedNLP).safeTransfer(msg.sender, wNLPAmount);

        emit WithdrawalCancelled(msg.sender, wNLPAmount);
    }

    /// @notice Set the withdrawal window
    /// @param newWindow New window duration in seconds
    function setWithdrawalWindow(uint256 newWindow) external onlyOwner {
        require(newWindow <= MAX_WITHDRAWAL_WINDOW, ErrorsLib.InvalidAmount());

        uint256 oldWindow = withdrawalWindow;
        withdrawalWindow = newWindow;

        emit WithdrawalWindowUpdated(oldWindow, newWindow);
    }

    /// @notice Get withdrawal request details for a user
    /// @param user The user address
    /// @return request The withdrawal request struct
    function getWithdrawalRequest(address user) external view returns (WithdrawalRequest memory request) {
        return withdrawalRequests[user];
    }

    function _claimWithdrawal(address user, address to) internal returns (uint256 underlyingAmount) {
        WithdrawalRequest storage request = withdrawalRequests[user];

        require(request.wNLPAmount > 0, ErrorsLib.ZeroAmount());
        require(block.timestamp >= request.claimableTime, "Not claimable");

        uint256 wNLPAmount = request.wNLPAmount;

        delete withdrawalRequests[user];

        // Update total unclaimed amount
        totalUnclaimedAmount -= wNLPAmount;

        // Execute withdrawal through WrappedNLP
        underlyingAmount = wrappedNLP.unwrapAndRedeem(wNLPAmount, to);

        emit WithdrawalClaimed(user, to, wNLPAmount, underlyingAmount);
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.0;

import "./Ownable.sol";

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2Step is Ownable {
    address private _pendingOwner;

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

    /**
     * @dev Returns the address of the pending owner.
     */
    function pendingOwner() public view virtual returns (address) {
        return _pendingOwner;
    }

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        _pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        delete _pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner");
        _transferOwnership(sender);
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/security/Pausable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    bool private _paused;

    /**
     * @dev Initializes the contract in unpaused state.
     */
    constructor() {
        _paused = false;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        require(!paused(), "Pausable: paused");
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        require(paused(), "Pausable: not paused");
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}
"
    },
    "src/WrappedNLP.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.28;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {IERC20Metadata as IERC20} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

import {NativeLPToken} from "./NativeLPToken.sol";
import {WithdrawQueue} from "./WithdrawQueue.sol";
import {SafeERC20} from "./libraries/SafeERC20.sol";
import {ErrorsLib} from "./libraries/ErrorsLib.sol";
import {ReentrancyGuardTransient} from "./libraries/ReentrancyGuardTransient.sol";

/**
 * @title WrappedNLP - Wrapped NativeLPToken
 * @dev Converts rebasing NativeLPToken into static-balance ERC20 for protocol compatibility
 * while preserving accumulated yield value.
 *
 * @notice Redemption Options:
 * - Queue-based: Free redemption via WithdrawQueue contract (time-delayed for JIT protection)
 * - Instant: Direct redemption with configurable fee (immediate, open to all users)
 */
contract WrappedNLP is ERC20, Ownable2Step, ReentrancyGuardTransient {
    using SafeERC20 for IERC20;

    /*//////////////////////////////////////////////////////////////////////////
                                    STATE VARIABLES
    //////////////////////////////////////////////////////////////////////////*/

    /// @notice Whether instant redeem is enabled
    bool public instantRedeemEnabled = true;

    /// @notice The Native LPToken address
    NativeLPToken public immutable nlp;

    /// @notice The underlying token of the NativeLPToken
    IERC20 public immutable underlying;

    /// @notice Address that receives instant redeem fees
    address public feeRecipient;

    /// @notice The authorized WithdrawQueue contract
    WithdrawQueue public withdrawQueue;

    /// @notice Minimum amount for instant redeem
    uint256 public constant MIN_INSTANT_REDEEM_AMOUNT = 1000;

    /// @notice Instant redeem fee in basis points (1 bip = 0.01%)
    uint256 public instantRedeemFeeBips;

    /// @notice Mapping of whitelisted addresses that can directly call unwrapAndRedeem
    mapping(address => bool) public redeemWhitelist;

    /*//////////////////////////////////////////////////////////////////////////
                                    EVENTS
    //////////////////////////////////////////////////////////////////////////*/

    /// @notice Event emitted when NativeLP tokens are wrapped into WrappedNLP tokens
    event Wrapped(address indexed user, uint256 nlpAmount, uint256 wNLPAmount);

    /// @notice Event emitted when underlying tokens are deposited and wrapped into WrappedNLP tokens
    event DepositAndWrapped(address indexed from, address indexed to, uint256 underlyingAmount, uint256 wNLPAmount);

    /// @notice Event emitted when WrappedNLP tokens are unwrapped and redeemed for underlying tokens
    event UnwrappedAndRedeemed(address indexed from, address indexed to, uint256 wNLPAmount, uint256 underlyingAmount);

    /// @notice Event emitted when instant redeem fee is charged
    event InstantRedeemFeeCollected(address indexed user, uint256 feeAmount);

    /// @notice Event emitted when fee recipient is updated
    event FeeRecipientUpdated(address indexed oldRecipient, address indexed newRecipient);

    /// @notice Event emitted when instant redeem is executed
    event InstantRedeemed(address indexed user, uint256 wNLPAmount, uint256 underlyingAmount, uint256 feeAmount);

    /// @notice Event emitted when withdraw queue address is updated
    event WithdrawQueueUpdated(address indexed oldQueue, address indexed newQueue);

    /// @notice Event emitted when redeem whitelist status is updated
    event RedeemWhitelistUpdated(address indexed account, bool status);

    /// @notice Event emitted when instant redeem fee bips is updated
    event InstantRedeemFeeBipsUpdated(uint256 oldFeeBips, uint256 newFeeBips);

    /// @notice Event emitted when instant redeem enabled status is updated
    event InstantRedeemEnabledUpdated(bool enabled);

    constructor(string memory _name, string memory _symbol, NativeLPToken _nlp) ERC20(_name, _symbol) {
        nlp = _nlp;
        underlying = nlp.underlying();
        feeRecipient = msg.sender;

        instantRedeemFeeBips = nlp.earlyWithdrawFeeBips();
    }

    /*//////////////////////////////////////////////////////////////////////////
                                    WRAP AND REDEEM
    //////////////////////////////////////////////////////////////////////////*/

    /// @notice Wraps NativeLP tokens into WrappedNLP tokens
    /// @param amount Amount of NativeLP tokens to deposit
    /// @return wNLPAmount Amount of WrappedNLP tokens received
    function wrap(uint256 amount) external nonReentrant returns (uint256 wNLPAmount) {
        require(amount > 0, ErrorsLib.ZeroAmount());

        wNLPAmount = nlp.getSharesByUnderlying(amount);

        IERC20(nlp).safeTransferFrom(msg.sender, address(this), amount);

        _mint(msg.sender, wNLPAmount);

        emit Wrapped(msg.sender, amount, wNLPAmount);
    }

    /// @notice Deposit underlying tokens directly into NLP and receive WrappedNLP tokens for a specified recipient
    /// @param to The address to receive the WrappedNLP tokens
    /// @param amount Amount of underlying tokens to deposit
    /// @return wNLPAmount Amount of WrappedNLP tokens received
    function depositAndWrap(address to, uint256 amount) external nonReentrant returns (uint256 wNLPAmount) {
        require(amount > 0, ErrorsLib.ZeroAmount());
        require(to != address(0), ErrorsLib.ZeroAddress());

        underlying.safeTransferFrom(msg.sender, address(this), amount);
        underlying.safeApprove(address(nlp), amount);

        wNLPAmount = nlp.deposit(amount);

        _mint(to, wNLPAmount);

        emit DepositAndWrapped(msg.sender, to, amount, wNLPAmount);
    }

    /// @notice Unwrap WrappedNLP tokens and redeem underlying tokens to a specified address
    /// @dev Only WithdrawQueue contract or whitelisted addresses can call this function
    /// @param wNLPAmount Amount of WrappedNLP tokens to unwrap and redeem
    /// @param to Address that will receive the underlying tokens
    /// @return underlyingAmount Amount of underlying tokens sent to the recipient
    function unwrapAndRedeem(uint256 wNLPAmount, address to) external nonReentrant returns (uint256 underlyingAmount) {
        require(wNLPAmount > 0, ErrorsLib.ZeroAmount());
        require(to != address(0), ErrorsLib.ZeroAddress());
        require(msg.sender == address(withdrawQueue) || redeemWhitelist[msg.sender], ErrorsLib.NotTrustedOperator());

        _burn(msg.sender, wNLPAmount);

        underlyingAmount = nlp.redeemTo(wNLPAmount, to);

        emit UnwrappedAndRedeemed(msg.sender, to, wNLPAmount, underlyingAmount);
    }

    /// @notice Instantly redeem WrappedNLP tokens for underlying tokens with fee
    /// @param wNLPAmount Amount of WrappedNLP tokens to redeem
    /// @param to Address that will receive the underlying tokens
    /// @return underlyingAmount Amount of underlying tokens received after fee
    function instantRedeem(uint256 wNLPAmount, address to) external nonReentrant returns (uint256 underlyingAmount) {
        require(instantRedeemEnabled, "Instant redeem disabled");
        require(wNLPAmount >= MIN_INSTANT_REDEEM_AMOUNT, "Below minimum instant redeem amount");
        require(to != address(0), ErrorsLib.ZeroAddress());

        _burn(msg.sender, wNLPAmount);

        underlyingAmount = nlp.redeemTo(wNLPAmount, address(this));

        uint256 fee = (underlyingAmount * instantRedeemFeeBips) / 10_000;

        if (fee > 0) {
            underlyingAmount -= fee;
            // Transfer fee to fee recipient
            underlying.safeTransfer(feeRecipient, fee);
            emit InstantRedeemFeeCollected(msg.sender, fee);
        }

        underlying.safeTransfer(to, underlyingAmount);

        emit InstantRedeemed(msg.sender, wNLPAmount, underlyingAmount, fee);
    }

    /*//////////////////////////////////////////////////////////////////////////
                                    VIEW FUNCTIONS
    //////////////////////////////////////////////////////////////////////////*/

    /// @notice Gets the number of decimals for this token
    /// @return The number of decimals, matching the underlying token
    function decimals() public view override returns (uint8) {
        return nlp.decimals();
    }

    /// @notice Calculates the amount of WrappedNLP tokens for a given amount of NativeLP tokens
    /// @param _nlpAmount Amount of NativeLP tokens
    /// @return Amount of WrappedNLP tokens
    function getWnlpByNlp(uint256 _nlpAmount) external view returns (uint256) {
        return nlp.getSharesByUnderlying(_nlpAmount);
    }

    /// @notice Calculates the amount of NLP tokens for a given amount of WrappedNLP tokens
    /// @param wNLPAmount Amount of WrappedNLP tokens
    /// @return Amount of NativeLP tokens
    function getNlpByWnlp(uint256 wNLPAmount) external view returns (uint256) {
        return nlp.getUnderlyingByShares(wNLPAmount);
    }

    /// @notice Returns the amount of NativeLP tokens per 1 WrappedNLP token
    /// @dev Used to calculate the exchange rate from WrappedNLP to NativeLP
    /// @return Amount of NativeLP tokens equivalent to 1 WrappedNLP token
    function nlpPerToken() external view returns (uint256) {
        return nlp.getUnderlyingByShares(10 ** decimals());
    }

    /// @notice Returns the amount of WrappedNLP tokens per 1 NativeLP token
    /// @dev Used to calculate the exchange rate from NativeLP to WrappedNLP
    /// @return Amount of WrappedNLP tokens equivalent to 1 NativeLP token
    function tokensPerNlp() external view returns (uint256) {
        return nlp.getSharesByUnderlying(10 ** decimals());
    }

    /*//////////////////////////////////////////////////////////////////////////
                                    ADMIN FUNCTIONS
    //////////////////////////////////////////////////////////////////////////*/

    /// @notice Sets the WithdrawQueue contract address
    /// @param _withdrawQueue The new WithdrawQueue contract address
    function setWithdrawQueue(address _withdrawQueue) external onlyOwner {
        require(_withdrawQueue != address(0), ErrorsLib.ZeroAddress());

        address oldQueue = address(withdrawQueue);
        withdrawQueue = WithdrawQueue(_withdrawQueue);

        emit WithdrawQueueUpdated(oldQueue, _withdrawQueue);
    }

    /// @notice Sets the redeem whitelist status for multiple addresses
    /// @param accounts The addresses to update
    /// @param statuses The new whitelist statuses corresponding to each account
    function setRedeemWhitelist(address[] calldata accounts, bool[] calldata statuses) external onlyOwner {
        for (uint256 i = 0; i < accounts.length; i++) {
            require(accounts[i] != address(0), ErrorsLib.ZeroAddress());

            redeemWhitelist[accounts[i]] = statuses[i];
            emit RedeemWhitelistUpdated(accounts[i], statuses[i]);
        }
    }

    /// @notice Sets the address that receives instant redeem fees
    /// @param _feeRecipient The new fee recipient address
    function setFeeRecipient(address _feeRecipient) external onlyOwner {
        require(_feeRecipient != address(0), ErrorsLib.ZeroAddress());

        address oldRecipient = feeRecipient;
        feeRecipient = _feeRecipient;

        emit FeeRecipientUpdated(oldRecipient, _feeRecipient);
    }

    /// @notice Sets the instant redeem fee in basis points
    /// @param _instantRedeemFeeBips The new instant redeem fee in basis points
    function setInstantRedeemFeeBips(uint256 _instantRedeemFeeBips) external onlyOwner {
        require(_instantRedeemFeeBips <= 2500, ErrorsLib.InvalidFeeBips());

        uint256 oldFeeBips = instantRedeemFeeBips;
        instantRedeemFeeBips = _instantRedeemFeeBips;

        emit InstantRedeemFeeBipsUpdated(oldFeeBips, _instantRedeemFeeBips);
    }

    /// @notice Sets whether instant redeem is enabled
    /// @param _enabled Whether instant redeem should be enabled
    function setInstantRedeemEnabled(bool _enabled) external onlyOwner {
        instantRedeemEnabled = _enabled;

        emit InstantRedeemEnabledUpdated(_enabled);
    }
}
"
    },
    "src/libraries/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.28;

import "openzeppelin/token/ERC20/IERC20.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)));
    }

    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        forceApprove(token, spender, 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 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 ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}
"
    },
    "src/libraries/ErrorsLib.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.28;

/// @title Custom error definitions for the protocol
/// @notice This library contains all the error definitions used throughout the contract
/// @dev The errors are arranged in alphabetical order
library ErrorsLib {
    /// @notice Thrown when actual amount deviation exceeds 10%
    error AmountDeviationExceeds();

    /// @notice Thrown when deposit amount is below minimum required
    error BelowMinimumDeposit();

    /// @notice Thrown when epoch update is attempted before minimum interval
    error EpochUpdateInCoolDown();

    /// @notice Thrown when LP token exchange rate increases more than allowed
    error ExchangeRateIncreaseTooMuch();

    /// @notice Thrown when feature is paused
    error FeaturePaused();

    /// @notice Thrown when there are insufficient funding fees to withdraw
    error InsufficientFundingFees();

    /// @notice Thrown when LP token shares are insufficient
    error InsufficientShares();

    /// @notice Thrown when LP token underlying is insufficient
    error InsufficientUnderlying();

    /// @notice Thrown when there is insufficient WETH9 to unwrap
    error InsufficientWETH9();

    /// @notice Thrown when an amount parameter is invalid
    error InvalidAmount();

    /// @notice Thrown when confidence factor E is invalid
    error InvalidConfidenceFactorE();

    /// @notice Thrown when confidence factor N is invalid
    error InvalidConfidenceFactorN();

    /// @notice Thrown when fee rate in basis points exceeds maximum (10000)
    error InvalidFeeBips();

    /// @notice Thrown when LP token address is invalid
    error InvalidLPToken();

    /// @notice Thrown when underlying are not supported in the credit vault
    error InvalidUnderlying();

    /// @notice Thrown when market (LP token) is invalid
    error InvalidMarket();

    /// @notice Thrown when position update amount is invalid
    error InvalidPositionUpdateAmount();

    /// @notice Thrown when the pool address is invalid Native pool
    error InvalidNativePool();

    /// @notice Thrown when signature verification fails
    error InvalidSignature();

    /// @notice Thrown when signer is not authorized
    error InvalidSigner();

    /// @notice Thrown when WETH9 unwrap amount is zero or exceeds balance
    error InvalidWETH9Amount();

    /// @notice Thrown when widget fee rate is invalid
    error InvalidWidgetFeeRate();

    /// @notice Thrown when wrap token is invalid
    error InvalidWrapToken();

    /// @notice Thrown when liquidator and recipient are the same
    error LiquidatorRecipientConflict();

    /// @notice Thrown when nonce is used
    error NonceUsed();

    /// @notice Thrown when output amount is less than minimum required
    error NotEnoughAmountOut(uint256 amountOut, uint256 amountOutMinimum);

    /// @notice Thrown the address is not a trader or liquidator
    error NotTraderOrLiquidator();

    /// @notice Error when caller is not a trusted operator
    error NotTrustedOperator();

    /// @notice Thrown when there is no yield to distribute
    error NoYieldToDistribute();

    /// @notice Thrown when caller is not the credit pool
    error OnlyCreditPool();

    /// @notice Thrown when caller is not the credit vault
    error OnlyCreditVault();

    /// @notice Thrown when caller is not the epoch updater
    error OnlyEpochUpdater();

    /// @notice Thrown when caller is not the fee withdrawer
    error OnlyFeeWithdrawer();

    /// @notice Thrown when caller is not an authorized liquidator
    error OnlyLiquidator();

    /// @notice Thrown when caller is not an LP token
    error OnlyLpToken();

    /// @notice Thrown when caller is not the native router
    error OnlyNativeRouter();

    /// @notice Thrown when caller is not an authorized trader
    error OnlyTrader();

    /// @notice Thrown when caller is not the WETH9 contract
    error OnlyWETH9();

    /// @notice Thrown when arithmetic operation would overflow
    error Overflow();

    /// @notice Thrown when LP pool has no deposits yet
    error PoolNotInitialized();

    /// @notice Thrown when quote has expired
    error QuoteExpired();

    /// @notice Thrown when rebalance limit is exceeded
    error RebalanceLimitExceeded();

    /// @notice Thrown when request has expired
    error RequestExpired();

    /// @notice Thrown when token is already listed
    error TokenAlreadyListed();

    /// @notice Thrown when trader, settler and recipient are the same
    error TraderRecipientConflict();

    /// @notice Thrown when transfer is in cooldown period
    error TransferInCooldown();

    /// @notice Thrown when transfer to self
    error TransferSelf();

    /// @notice Thrown when transfer to current contract
    error TransferToContract();

    /// @notice Thrown when unexpected msg.value is sent
    error UnexpectedMsgValue();

    /// @notice Thrown when zero address is provided
    error ZeroAddress();

    /// @notice Thrown when amount is zero
    error ZeroAmount();

    /// @notice Thrown when input is zero or empty
    error ZeroInput();
}
"
    },
    "src/libraries/ReentrancyGuardTransient.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

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

// Refer from OpenZeppelin https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v5.1/contracts/utils/ReentrancyGuardTransient.sol

/**
 * @dev Variant of {ReentrancyGuard} that uses transient storage.
 */
abstract contract ReentrancyGuardTransient {
    using TStorage for bytes32;

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

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

    /**
     * @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 {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if (_reentrancyGuardEntered()) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        REENTRANCY_GUARD_STORAGE.tstore(true);
    }

    function _nonReentrantAfter() private {
        REENTRANCY_GUARD_STORAGE.tstore(false);
    }

    /**
     * @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) {
        return REENTRANCY_GUARD_STORAGE.tload();
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/access/Ownable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.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.
 *
 * By default, the owner account will be the one that deploys the contract. 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 Ownable is Context {
    address private _owner;

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

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @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) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @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 {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/Context.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
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 amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

    /**
     * @dev Moves `amount` 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 amount) 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 `amount` 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 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` 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 amount) external returns (bool);
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.0;

import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.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}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * 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 ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20, IERC20Metadata {
    mapping(address => uint256) private _balances;

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

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these 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 override returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override 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 override returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override 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 `amount`.
     */
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `amount` 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 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * 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 `amount`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
     * @dev Moves `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.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     */
    function _transfer(address from, address to, uint256 amount) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            _balances[to] += amount;
        }

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        unchecked {
            // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
            _balances[account] += amount;
        }
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
            // Overflow not possible: amount <= accountBalance <= totalSupply.
            _totalSupply -= amount;
        }

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` 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.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
     *
     * Does not update the allowance amount in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Might emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * has been transferred to `to`.
     * - when `from` is zero, `amount` tokens have been minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}
"
    },
    "src/NativeLPToken.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.28;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20Metadata as IERC20} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

import "./libraries/ConstantsLib.sol";
import {CreditVault} from "./CreditVault.sol";
import {ErrorsLib} from "./libraries/ErrorsLib.sol";
import {ReentrancyGuardTransient} from "./libraries/ReentrancyGuardTransient.sol";

/// @title NativeLPToken - Yield-bearing LP token contract
/// @notice A token contract that represents liquidity provider positions and distributes yield
/// @dev This contract manages LP shares and underlying assets, accruing yield based on protocol revenue
contract NativeLPToken is ERC20, Ownable2Step, ReentrancyGuardTransient {
    using SafeERC20 for IERC20;

    /*//////////////////////////////////////////////////////////////////////////
                                     STATE VARIABLES
    //////////////////////////////////////////////////////////////////////////*/

    /// @notice Whether deposit operations are paused
    bool public depositPaused;

    /// @notice Whether redeem operations are paused
    bool public redeemPaused;

    /// @notice The underlying token that this LP token represents
    IERC20 public underlying;

    /// @notice The address of the credit vault contract
    address public creditVault;

    /// @notice The number of decimals for this token, matching the underlying token's decimals
    uint8 private _decimals;

    /// @notice Total amount of underlying assets deposited by LPs
    uint256 public totalUnderlying;

    /// @notice Total number of shares issued
    uint256 public totalShares;

    /// @notice Early withdrawal fee in basis points (1 bip = 0.01%)
    /// @dev Applied to prevent front-running by users who deposit right before yield distribution and immediately redeem after
    uint256 public earlyWithdrawFeeBips;

    /// @notice Accumulated early withdrawal fee
    uint256 public accEarlyWithdrawFee;

    /// @notice Minimum time interval between deposit and redeem (in seconds)
    uint256 public minRedeemInterval;

    /// @notice Minimum amount required for deposits
    uint256 public minDeposit;

    /// @notice Mapping of user addresses to their share balances
    mapping(address => uint256) public shares;

    /// @notice Mapping of trusted operators who can call depositFor and redeemTo functions
    mapping(address => bool) public trustedOperators;

    /// @notice Mapping of user addresses to their last deposit timestamp
    mapping(address => uint256) public lastDepositTimestamp;

    /// @notice Mapping of addresses exempt from redeem cooldown period and early withdrawal fees
    mapping(address => bool) public redeemCooldownExempt;

    /*//////////////////////////////////////////////////////////////////////////
                                        EVENTS
    //////////////////////////////////////////////////////////////////////////*/

    /// @notice Event emitted when deposit operation is paused
    event DepositPaused();

    /// @notice Event emitted when deposit operation is unpaused
    event DepositUnpaused();

    /// @notice Event emitted when redeem operation is paused
    event RedeemPaused();

    /// @notice Event emitted when redeem operation is unpaused
    event RedeemUnpaused();

    /// @notice Event emitted when yield is distributed to LP holders
    event YieldDistributed(uint256 yieldAmount);

    /// @notice Event emitted when minimum redeem interval is updated
    event MinRedeemIntervalUpdated(uint256 newInterval);

    /// @notice Event emitted when shares are transferred between addresses
    event TransferShares(address indexed from, address indexed to, uint256 shares);

    /// @notice Event emitted when new shares are minted
    event SharesMinted(address indexed from, address indexed to, uint256 shares, uint256 underlyingAmount);

    /// @notice Event emitted when shares are burned
    event SharesBurned(address indexed from, address indexed to, uint256 shares, uint256 underlyingAmount);

    /// @notice Event emitted when minimum deposit amount is updated
    event MinDepositUpdated(uint256 oldAmount, uint256 newAmount);

    /// @notice Event emitted when early withdraw fee is updated

Tags:
ERC20, Multisig, Swap, Liquidity, Yield, Upgradeable, Multi-Signature, Factory|addr:0x225ac433c6fbaf55542b2108255b2979b1130525|verified:true|block:23675075|tx:0xdf51f40bf197b0a3cfcb522acd3a9fcc8d28abacc0432b4ef5af8537796742f3|first_check:1761652275

Submitted on: 2025-10-28 12:51:17

Comments

Log in to comment.

No comments yet.