S Price: $0.564444 (-0.09%)

Contract Diff Checker

Contract Name:
GeneralTokenDispenserV1

Contract Source Code:

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../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.
 *
 * 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 Ownable is Context {
    address private _owner;

    /**
     * @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.
     */
    constructor(address initialOwner) {
        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) {
        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 {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.20;

import {Ownable} from "./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.
 *
 * The initial owner is specified at deployment time in the constructor for `Ownable`. 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();
        if (pendingOwner() != sender) {
            revert OwnableUnauthorizedAccount(sender);
        }
        _transferOwnership(sender);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @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 value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

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

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

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

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 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 {
    using Address for address;

    /**
     * @dev An operation with an ERC20 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 Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    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.
     */
    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.
     */
    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).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            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 silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedInnerCall();

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert AddressInsufficientBalance(address(this));
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert FailedInnerCall();
        }
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
     * unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {FailedInnerCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert FailedInnerCall();
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @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;
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

interface IFundingRateModel {
  // return value is the "funding paid by heavier side" in PRECISION per OI (heavier side) per second
  // e.g : (0.01 * PRECISION) = Paying (heavier) side (as a whole) pays 1% of funding per second for each OI unit
  function getFundingRate(
    uint256 pairId,
    uint256 openInterestLong,
    uint256 openInterestShort,
    uint256 pairMaxOpenInterest
  ) external view returns (uint256);
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

interface IInterestRateModel {
  // Returns asset/second of interest per borrowed unit
  // e.g : (0.01 * PRECISION) = 1% of interest per second
  function getBorrowRate(uint256 utilization) external view returns (uint256);
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import "./LexErrors.sol";
import "./LexPoolAdminEnums.sol";
import "./IPoolAccountantV1.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface LexPoolStructs {
  struct PendingDeposit {
    uint256 amount;
    uint256 minAmountOut;
  }

  struct PendingRedeem {
    uint256 amount;
    uint256 minAmountOut;
    uint256 maxAmountOut;
  }
}

interface LexPoolEvents is LexPoolAdminEnums {
  event NewEpoch(
    uint256 epochId,
    int256 reportedUnrealizedPricePnL,
    uint256 exchangeRate,
    uint256 virtualUnderlyingBalance,
    uint256 totalSupply
  );

  event AddressUpdated(LexPoolAddressesEnum indexed enumCode, address a);
  event NumberUpdated(LexPoolNumbersEnum indexed enumCode, uint value);
  event DepositRequest(
    address indexed user,
    uint256 amount,
    uint256 minAmountOut,
    uint256 processingEpoch
  );
  event RedeemRequest(
    address indexed user,
    uint256 amount,
    uint256 minAmountOut,
    uint256 processingEpoch
  );
  event ProcessedDeposit(
    address indexed user,
    bool deposited,
    uint256 depositedAmount
  );
  event ProcessedRedeem(
    address indexed user,
    bool redeemed,
    uint256 withdrawnAmount // Underlying amount
  );
  event CanceledDeposit(
    address indexed user,
    uint256 epoch,
    uint256 cancelledAmount
  );
  event CanceledRedeem(
    address indexed user,
    uint256 epoch,
    uint256 cancelledAmount
  );
  event ImmediateDepositAllowedToggled(bool indexed value);
  event ImmediateDeposit(
    address indexed depositor,
    uint256 depositAmount,
    uint256 mintAmount
  );
  event ReservesWithdrawn(
    address _to,
    uint256 interestShare,
    uint256 totalFundingShare
  );
}

interface ILexPoolFunctionality is
  IERC20,
  LexPoolStructs,
  LexPoolEvents,
  LexErrors
{
  function setPoolAccountant(
    IPoolAccountantFunctionality _poolAccountant
  ) external;

  function setPnlRole(address pnl) external;

  function setMaxExtraWithdrawalAmountF(uint256 maxExtra) external;

  function setEpochsDelayDeposit(uint256 delay) external;

  function setEpochsDelayRedeem(uint256 delay) external;

  function setEpochDuration(uint256 duration) external;

  function setMinDepositAmount(uint256 amount) external;

  function toggleImmediateDepositAllowed() external;

  function reduceReserves(
    address _to
  ) external returns (uint256 interestShare, uint256 totalFundingShare);

  function requestDeposit(
    uint256 amount,
    uint256 minAmountOut,
    bytes32 domain,
    bytes32 referralCode
  ) external;

  function requestDepositViaIntent(
    address user,
    uint256 amount,
    uint256 minAmountOut,
    bytes32 domain,
    bytes32 referralCode
  ) external;

  function requestRedeem(uint256 amount, uint256 minAmountOut) external;

  function requestRedeemViaIntent(
    address user,
    uint256 amount,
    uint256 minAmountOut
  ) external;

  function processDeposit(
    address[] memory users
  )
    external
    returns (
      uint256 amountDeposited,
      uint256 amountCancelled,
      uint256 counterDeposited,
      uint256 counterCancelled
    );

  function cancelDeposits(
    address[] memory users,
    uint256[] memory epochs
  ) external;

  function processRedeems(
    address[] memory users
  )
    external
    returns (
      uint256 amountRedeemed,
      uint256 amountCancelled,
      uint256 counterDeposited,
      uint256 counterCancelled
    );

  function cancelRedeems(
    address[] memory users,
    uint256[] memory epochs
  ) external;

  function nextEpoch(
    int256 totalUnrealizedPricePnL
  ) external returns (uint256 newExchangeRate);

  function currentVirtualUtilization() external view returns (uint256);

  function currentVirtualUtilization(
    uint256 totalBorrows,
    uint256 totalReserves,
    int256 unrealizedFunding
  ) external view returns (uint256);

  function virtualBalanceForUtilization() external view returns (uint256);

  function virtualBalanceForUtilization(
    uint256 extraAmount,
    int256 unrealizedFunding
  ) external view returns (uint256);

  function underlyingBalanceForExchangeRate() external view returns (uint256);

  function sendAssetToTrader(address to, uint256 amount) external;

  function isUtilizationForLPsValid() external view returns (bool);
}

interface ILexPoolV1 is ILexPoolFunctionality {
  function name() external view returns (string memory);

  function symbol() external view returns (string memory);

  function SELF_UNIT_SCALE() external view returns (uint);

  function underlyingDecimals() external view returns (uint256);

  function poolAccountant() external view returns (address);

  function underlying() external view returns (IERC20);

  function tradingFloor() external view returns (address);

  function currentEpoch() external view returns (uint256);

  function currentExchangeRate() external view returns (uint256);

  function nextEpochStartMin() external view returns (uint256);

  function epochDuration() external view returns (uint256);

  function minDepositAmount() external view returns (uint256);

  function epochsDelayDeposit() external view returns (uint256);

  function epochsDelayRedeem() external view returns (uint256);

  function immediateDepositAllowed() external view returns (bool);

  function pendingDeposits(
    uint epoch,
    address account
  ) external view returns (PendingDeposit memory);

  function pendingRedeems(
    uint epoch,
    address account
  ) external view returns (PendingRedeem memory);

  function pendingDepositAmount() external view returns (uint256);

  function pendingWithdrawalAmount() external view returns (uint256);
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

interface ILynxVersionedContract {
  /**
   * @notice Returns the name of the contract
   */
  function getContractName() external view returns (string memory);

  /**
   * @notice Returns the version of the contract
   * @dev units are scaled by 1000 (1,000 = 1.00, 1,120 = 1.12)
   */
  function getContractVersion() external view returns (string memory);
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import "./LexErrors.sol";
import "./ILexPoolV1.sol";
import "./IInterestRateModel.sol";
import "./IFundingRateModel.sol";
import "./TradingEnumsV1.sol";

interface PoolAccountantStructs {
  // @note To be used for passing information in function calls
  struct PositionRegistrationParams {
    uint256 collateral;
    uint32 leverage;
    bool long;
    uint64 openPrice;
    uint64 tp;
  }

  struct PairFunding {
    // Slot 0
    int256 accPerOiLong; // 32 bytes -- Underlying Decimals
    // Slot 1
    int256 accPerOiShort; // 32 bytes -- Underlying Decimals
    // Slot 2
    uint256 lastUpdateTimestamp; // 32 bytes
  }

  struct TradeInitialAccFees {
    // Slot 0
    uint256 borrowIndex; // 32 bytes
    // Slot 1
    int256 funding; // 32 bytes -- underlying units -- Underlying Decimals
  }

  struct PairOpenInterest {
    // Slot 0
    uint256 long; // 32 bytes -- underlying units -- Dynamic open interest for long positions
    // Slot 1
    uint256 short; // 32 bytes -- underlying units -- Dynamic open interest for short positions
  }

  // This struct is not kept in storage
  struct PairFromTo {
    string from;
    string to;
  }

  struct Pair {
    // Slot 0
    uint16 id; // 02 bytes
    uint16 groupId; // 02 bytes
    uint16 feeId; // 02 bytes
    uint32 minLeverage; // 04 bytes
    uint32 maxLeverage; // 04 bytes
    uint32 maxBorrowF; // 04 bytes -- FRACTION_SCALE (5)
    // Slot 1
    uint256 maxPositionSize; // 32 bytes -- underlying units
    // Slot 2
    uint256 maxGain; // 32 bytes -- underlying units
    // Slot 3
    uint256 maxOpenInterest; // 32 bytes -- Underlying units
    // Slot 4
    uint256 maxSkew; // 32 bytes -- underlying units
    // Slot 5
    uint256 minOpenFee; // 32 bytes -- underlying units. MAX_UINT means use the default group level value
    // Slot 6
    uint256 minPerformanceFee; // 32 bytes -- underlying units
  }

  struct Group {
    // Slot 0
    uint16 id; // 02 bytes
    uint32 minLeverage; // 04 bytes
    uint32 maxLeverage; // 04 bytes
    uint32 maxBorrowF; // 04 bytes -- FRACTION_SCALE (5)
    // Slot 1
    uint256 maxPositionSize; // 32 bytes (Underlying units)
    // Slot 2
    uint256 minOpenFee; // 32 bytes (Underlying uints). MAX_UINT means use the default global level value
  }

  struct Fee {
    // Slot 0
    uint16 id; // 02 bytes
    uint32 openFeeF; // 04 bytes -- FRACTION_SCALE (5) (Fraction of leveraged pos)
    uint32 closeFeeF; // 04 bytes -- FRACTION_SCALE (5) (Fraction of leveraged pos)
    uint32 performanceFeeF; // 04 bytes -- FRACTION_SCALE (5) (Fraction of performance)
  }
}

interface PoolAccountantEvents is PoolAccountantStructs {
  event PairAdded(
    uint256 indexed id,
    string indexed from,
    string indexed to,
    Pair pair
  );
  event PairUpdated(uint256 indexed id, Pair pair);

  event GroupAdded(uint256 indexed id, string indexed groupName, Group group);
  event GroupUpdated(uint256 indexed id, Group group);

  event FeeAdded(uint256 indexed id, string indexed name, Fee fee);
  event FeeUpdated(uint256 indexed id, Fee fee);

  event TradeInitialAccFeesStored(
    bytes32 indexed positionId,
    uint256 borrowIndex,
    // uint256 rollover,
    int256 funding
  );

  event AccrueFunding(
    uint256 indexed pairId,
    int256 valueLong,
    int256 valueShort
  );

  event ProtocolFundingShareAccrued(
    uint16 indexed pairId,
    uint256 protocolFundingShare
  );
  // event AccRolloverFeesStored(uint256 pairIndex, uint256 value);

  event FeesCharged(
    bytes32 indexed positionId,
    address indexed trader,
    uint16 indexed pairId,
    PositionRegistrationParams positionRegistrationParams,
    //        bool long,
    //        uint256 collateral, // Underlying Decimals
    //        uint256 leverage,
    int256 profitPrecision, // PRECISION
    uint256 interest,
    int256 funding, // Underlying Decimals
    uint256 closingFee,
    uint256 tradeValue
  );

  event PerformanceFeeCharging(
    bytes32 indexed positionId,
    uint256 performanceFee
  );

  event MaxOpenInterestUpdated(uint256 pairIndex, uint256 maxOpenInterest);

  event AccrueInterest(
    uint256 cash,
    uint256 totalInterestNew,
    uint256 borrowIndexNew,
    uint256 interestShareNew
  );

  event Borrow(
    uint256 indexed pairId,
    uint256 borrowAmount,
    uint256 newTotalBorrows
  );

  event Repay(
    uint256 indexed pairId,
    uint256 repayAmount,
    uint256 newTotalBorrows
  );
}

interface IPoolAccountantFunctionality is
  PoolAccountantStructs,
  PoolAccountantEvents,
  LexErrors,
  TradingEnumsV1
{
  function setTradeIncentivizer(address _tradeIncentivizer) external;

  function setMaxGainF(uint256 _maxGainF) external;

  function setFrm(IFundingRateModel _frm) external;

  function setMinOpenFee(uint256 min) external;

  function setLexPartF(uint256 partF) external;

  function setFundingRateMax(uint256 maxValue) external;

  function setLiquidationThresholdF(uint256 threshold) external;

  function setLiquidationFeeF(uint256 fee) external;

  function setIrm(IInterestRateModel _irm) external;

  function setIrmHard(IInterestRateModel _irm) external;

  function setInterestShareFactor(uint256 factor) external;
  
  function setFundingShareFactor(uint256 factor) external;

  function setBorrowRateMax(uint256 rate) external;

  function setMaxTotalBorrows(uint256 maxBorrows) external;

  function setMaxVirtualUtilization(uint256 _maxVirtualUtilization) external;

  function resetTradersPairGains(uint256 pairId) external;

  function addGroup(Group calldata _group) external;

  function updateGroup(Group calldata _group) external;

  function addFee(Fee calldata _fee) external;

  function updateFee(Fee calldata _fee) external;

  function addPair(Pair calldata _pair) external;

  function addPairs(Pair[] calldata _pairs) external;

  function updatePair(Pair calldata _pair) external;

  function readAndZeroReserves()
    external
    returns (uint256 accumulatedInterestShare,
             uint256 accFundingShare);

  function registerOpenTrade(
    bytes32 positionId,
    address trader,
    uint16 pairId,
    uint256 collateral,
    uint32 leverage,
    bool long,
    uint256 tp,
    uint256 openPrice
  ) external returns (uint256 fee, uint256 lexPartFee);

  function registerCloseTrade(
    bytes32 positionId,
    address trader,
    uint16 pairId,
    PositionRegistrationParams calldata positionRegistrationParams,
    uint256 closePrice,
    PositionCloseType positionCloseType
  )
    external
    returns (
      uint256 closingFee,
      uint256 tradeValue,
      int256 profitPrecision,
      uint finalClosePrice
    );

  function registerUpdateTp(
    bytes32 positionId,
    address trader,
    uint16 pairId,
    uint256 collateral,
    uint32 leverage,
    bool long,
    uint256 openPrice,
    uint256 oldTriggerPrice,
    uint256 triggerPrice
  ) external;

  // function registerUpdateSl(
  //     address trader,
  //     uint256 pairIndex,
  //     uint256 index,
  //     uint256 collateral,
  //     uint256 leverage,
  //     bool long,
  //     uint256 openPrice,
  //     uint256 triggerPrice
  // ) external returns (uint256 fee);

  function accrueInterest()
    external
    returns (
      uint256 totalInterestNew,
      uint256 interestShareNew,
      uint256 borrowIndexNew
    );

  // Limited only for the LexPool
  function accrueInterest(
    uint256 availableCash
  )
    external
    returns (
      uint256 totalInterestNew,
      uint256 interestShareNew,
      uint256 borrowIndexNew
    );

  function getTradeClosingValues(
    bytes32 positionId,
    uint16 pairId,
    PositionRegistrationParams calldata positionRegistrationParams,
    uint256 closePrice,
    bool isLiquidation
  )
    external
    returns (
      uint256 tradeValue, // Underlying Decimals
      uint256 safeClosingFee,
      int256 profitPrecision,
      uint256 interest,
      int256 funding
    );

  function getTradeLiquidationPrice(
    bytes32 positionId,
    uint16 pairId,
    uint256 openPrice, // PRICE_SCALE (8)
    uint256 tp,
    bool long,
    uint256 collateral, // Underlying Decimals
    uint32 leverage
  )
    external
    returns (
      uint256 // PRICE_SCALE (8)
    );

  function calcTradeDynamicFees(
    bytes32 positionId,
    uint16 pairId,
    bool long,
    uint256 collateral,
    uint32 leverage,
    uint256 openPrice,
    uint256 tp
  ) external returns (uint256 interest, int256 funding);

  function unrealizedFunding() external view returns (int256);

  function totalBorrows() external view returns (uint256);

  function interestShare() external view returns (uint256);
  
  function fundingShare() external view returns (uint256);

  function totalReservesView() external view returns (uint256);

  function borrowsAndInterestShare()
    external
    view
    returns (uint256 totalBorrows, uint256 totalInterestShare);

  function pairTotalOpenInterest(
    uint256 pairIndex
  ) external view returns (int256);

  function pricePnL(
    uint256 pairId,
    uint256 price
  ) external view returns (int256);

  function getAllSupportedPairIds() external view returns (uint16[] memory);

  function getAllSupportedGroupsIds() external view returns (uint16[] memory);

  function getAllSupportedFeeIds() external view returns (uint16[] memory);
}

interface IPoolAccountantV1 is IPoolAccountantFunctionality {
  function totalBorrows() external view returns (uint256);

  function maxTotalBorrows() external view returns (uint256);

  function pairBorrows(uint256 pairId) external view returns (uint256);

  function groupBorrows(uint256 groupId) external view returns (uint256);

  function pairMaxBorrow(uint16 pairId) external view returns (uint256);

  function groupMaxBorrow(uint16 groupId) external view returns (uint256);

  function lexPool() external view returns (ILexPoolV1);

  function maxGainF() external view returns (uint256);

  function interestShareFactor() external view returns (uint256);
  
  function fundingShareFactor() external view returns (uint256);

  function frm() external view returns (IFundingRateModel);

  function irm() external view returns (IInterestRateModel);

  function pairs(uint16 pairId) external view returns (Pair memory);

  function groups(uint16 groupId) external view returns (Group memory);

  function fees(uint16 feeId) external view returns (Fee memory);

  function openInterestInPair(
    uint pairId
  ) external view returns (PairOpenInterest memory);

  function minOpenFee() external view returns (uint256);

  function liquidationThresholdF() external view returns (uint256);

  function liquidationFeeF() external view returns (uint256);

  function lexPartF() external view returns (uint256);

  function tradersPairGains(uint256 pairId) external view returns (int256);

  function calcBorrowAmount(
    uint256 collateral,
    uint256 leverage,
    bool long,
    uint256 openPrice,
    uint256 tp
  ) external pure returns (uint256);
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

interface LexErrors {
  enum CapType {
    NONE, // 0
    MIN_OPEN_FEE, // 1
    MAX_POS_SIZE_PAIR, // 2
    MAX_POS_SIZE_GROUP, // 3
    MAX_LEVERAGE, // 4
    MIN_LEVERAGE, // 5
    MAX_VIRTUAL_UTILIZATION, // 6
    MAX_OPEN_INTEREST, // 7
    MAX_ABS_SKEW, // 8
    MAX_BORROW_PAIR, // 9
    MAX_BORROW_GROUP, // 10
    MIN_DEPOSIT_AMOUNT, // 11
    MAX_ACCUMULATED_GAINS, // 12
    BORROW_RATE_MAX, // 13
    FUNDING_RATE_MAX, // 14
    MAX_POTENTIAL_GAIN, // 15
    MAX_TOTAL_BORROW, // 16
    MIN_PERFORMANCE_FEE // 17
    //...
  }
  error CapError(CapType, uint256 value);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

interface LexPoolAdminEnums {
  enum LexPoolAddressesEnum {
    none,
    poolAccountant,
    pnlRole
  }

  enum LexPoolNumbersEnum {
    none,
    maxExtraWithdrawalAmountF,
    epochsDelayDeposit,
    epochsDelayRedeem,
    epochDuration,
    minDepositAmount
  }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

interface TradingEnumsV1 {
  enum PositionPhase {
    NONE,
    OPEN_MARKET,
    OPEN_LIMIT,
    OPENED,
    CLOSE_MARKET,
    CLOSED
  }

  enum OpenOrderType {
    NONE,
    MARKET,
    LIMIT
  }
  enum CloseOrderType {
    NONE,
    MARKET
  }
  enum FeeType {
    NONE,
    OPEN_FEE,
    CLOSE_FEE,
    TRIGGER_FEE
  }
  enum LimitTrigger {
    NONE,
    TP,
    SL,
    LIQ
  }
  enum PositionField {
    NONE,
    TP,
    SL
  }

  enum PositionCloseType {
    NONE,
    TP,
    SL,
    LIQ,
    MARKET
  }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import {IDispenserPullSourceV1} from "../interfaces/IDispenserPullSourceV1.sol";
import {TokenHandlingContractUtils} from "../../../Utils/TokenHandlingContractUtils.sol";

abstract contract AbstractPullSourceV1 is
  TokenHandlingContractUtils,
  IDispenserPullSourceV1
{
  // ***** Events *****

  event DownstreamDispenserSet(
    address indexed previousDownstreamDispenser,
    address indexed newDownstreamDispenser
  );
  event TokenDispensed(
    address indexed token,
    address indexed account,
    address indexed receiver,
    uint256 amount
  );
  event TokenCredited(
    address indexed token,
    address indexed account,
    uint256 amountCredited
  );
  event TokenDiscredited(
    address indexed token,
    address indexed account,
    uint256 amountCredited
  );

  // ***** Storage *****

  address public downstreamDispenser;

  // @dev : This is the total amount of tokens that are pending to be distributed.
  // token => total pending distributions
  mapping(address => uint256) public totalPendingDispensations;

  // token => account => pending amount
  mapping(address => mapping(address => uint256))
    public pendingDispensationsForAccount;

  // ***** Modifiers *****

  modifier onlyDownstreamDispenser() {
    require(msg.sender == downstreamDispenser, "NOT_DOWNSTREAM_DISPENSER");
    _;
  }

  // ***** Views *****

  /**
   * @notice Returns whether the dispenser is matching the source.
   * @return True if interface matching
   */
  function isDispenserMatchingSourceV1() external pure override returns (bool) {
    return true;
  }

  /**
   * @notice Returns the pending amount for the account if it were to be pulled now without any state.
   * @param _token The token to get the pending amount for.
   * @param _account The account to get the pending amount for.
   * @return The pending amount for the account.
   */
  function getPendingAmountForAccountView(
    address _token,
    address _account
  ) public view override returns (uint256) {
    return
      pendingDispensationsForAccount[_token][_account] +
      _getExternalPendingAmountForAccountView(_token, _account);
  }

  // ***** View-Like *****

  /**
   * @notice Returns the pending amount for the account if it were to be pulled now.
   * @dev This function is not a view as it may update the state.
   * @param _token The token to get the pending amount for.
   * @param _account The account to get the pending amount for.
   * @return The pending amount for the account.
   */
  function getPendingAmountForAccount(
    address _token,
    address _account
  ) external override returns (uint256) {
    _pullFromAllSourcesAndRegisterInternal(_token, _account);
    return pendingDispensationsForAccount[_token][_account];
  }

  // ***** Constructor *****

  constructor() {}

  // ***** Downstream Dispenser Functions *****

  /**
   * @notice Requests to receive the entire amount '_account' is entitled to
   * @dev This function is expected to be called by the dispenser, the source is expected to transfer tokens to the dispenser
   *      to be credited for '_account'
   * @param _token The token to request.
   * @param _account The account to request the token for.
   */
  function pullTokenForAccount(
    address _token,
    address _account
  ) external override onlyDownstreamDispenser {
    // Sanity
    require(downstreamDispenser != address(0), "NO_DOWNSTREAM_DISPENSER");
    _pullTokenForAccountInternal(_token, _account);
  }

  /**
   * @notice Requests to receive the entire amount '_account' is entitled to
   * @dev This function is expected to be called by the dispenser, the source is expected to transfer tokens to the dispenser
   *      to be credited for '_account'
   * @param _token The token to request.
   * @param _accounts The accounts to request the token for.
   * @return amounts an array of amounts that were pulled for each account
   */
  function pullTokenForAccounts(
    address _token,
    address[] calldata _accounts
  )
    external
    override
    onlyDownstreamDispenser
    returns (uint256[] memory amounts)
  {
    // Sanity
    require(downstreamDispenser != address(0), "NO_DOWNSTREAM_DISPENSER");
    amounts = new uint256[](_accounts.length);
    for (uint256 i = 0; i < _accounts.length; i++) {
      amounts[i] = _pullTokenForAccountInternal(_token, _accounts[i]);
    }
  }

  // ***** Internal Admin Functions *****

  function setDownstreamDispenserInternal(
    address _downstreamDispenser
  ) internal {
    // Storage
    address previousDownstreamDispenser = downstreamDispenser;
    downstreamDispenser = _downstreamDispenser;

    // Event
    emit DownstreamDispenserSet(
      previousDownstreamDispenser,
      _downstreamDispenser
    );
  }

  // ***** Internal Pulling Functions *****

  /**
   * @notice Implements the behaviour expected from this contract as a 'DispensesPullSource'
   * @dev will pull from all sources and send and entitlement to 'downstreamDispenser'
   */
  function _pullTokenForAccountInternal(
    address _token,
    address _account
  ) internal returns (uint256 amountDispensedToAccount) {
    // First pull from all sources available to this contract
    _pullFromAllSourcesAndRegisterInternal(_token, _account);

    // Then, dispense to 'downstreamDispenser'
    amountDispensedToAccount = dispensePendingTokenToReceiverInternal(
      address(_token),
      _account,
      downstreamDispenser
    );
  }

  /**
   * @notice Will send all of _account pending _token to _receiver
   */
  function dispensePendingTokenToReceiverInternal(
    address _token,
    address _account,
    address _receiver
  ) internal returns (uint256 amountDispensed) {
    // Sanity
    require(_receiver != address(0), "CANNOT_DISPENSE_TO_ZERO");

    // Zero pending amount
    amountDispensed = pendingDispensationsForAccount[_token][_account];
    pendingDispensationsForAccount[_token][_account] = 0;

    totalPendingDispensations[_token] -= amountDispensed;

    // Internal hook
    onTokenDispensedToReceiverInternal(
      _token,
      _account,
      _receiver,
      amountDispensed
    );

    // Send token
    sendTokens(_token, _receiver, amountDispensed);

    // Event
    emit TokenDispensed(_token, _account, _receiver, amountDispensed);
  }

  // ***** Internal Credit/Discredit Functions *****

  /**
   * @notice Increases pending amount for account
   */
  function creditTokenInternal(
    address _token,
    address _account,
    uint256 _amount
  ) internal {
    uint256 newAccountPending = pendingDispensationsForAccount[_token][
      _account
    ] + _amount;
    uint256 newTokenPending = totalPendingDispensations[_token] + _amount;

    pendingDispensationsForAccount[_token][_account] = newAccountPending;
    totalPendingDispensations[_token] = newTokenPending;

    emit TokenCredited(_token, _account, _amount);
  }

  /**
   * @notice Reduces pending amount for account
   * @dev 0 means all. Will revert if amount is greater than pending amount
   */
  function discreditTokenInternal(
    address _token,
    address _account,
    uint256 _amount,
    address _destination
  ) internal {
    // Start by pulling from all sources
    _pullFromAllSourcesAndRegisterInternal(_token, _account);

    uint256 currentPending = pendingDispensationsForAccount[_token][_account];
    uint256 amountReduced = 0;

    if (_amount == 0) {
      pendingDispensationsForAccount[_token][_account] = 0;
      amountReduced = currentPending;
    } else {
      require(currentPending >= _amount, "AMOUNT_EXCEEDS_PENDING");
      pendingDispensationsForAccount[_token][_account] =
        currentPending -
        _amount;
      amountReduced = _amount;
    }

    // Update total pending and avoid underflow
    uint256 currentTotalPending = totalPendingDispensations[_token];
    totalPendingDispensations[_token] = amountReduced > currentTotalPending
      ? 0
      : currentTotalPending - amountReduced;

    sendTokens(_token, _destination, amountReduced);

    emit TokenDiscredited(_token, _account, amountReduced);
  }

  // ***** Internal Overridable Functions *****

  /**
   * @notice Pulls the pending amount for the account from the external sources.
   * @dev This function is virtual and can be overridden and implemented by the child contract.
   * @param _token The token to pull the pending amount for.
   * @param _account The account to pull the pending amount for.
   * @return externalPulledAmount The amount pulled.
   */
  function _pullFromAllSourcesAndRegisterInternal(
    address _token,
    address _account
  ) internal virtual returns (uint256 externalPulledAmount) {}

  /**
   * @notice Reads the pending amount for the account from the external sources.
   * @dev This function is virtual and can be overridden and implemented by the child contract.
   * @param _token The token to pull the pending amount for.
   * @param _account The account to pull the pending amount for.
   * @return externalPendingAmount The amount read.
   */
  function _getExternalPendingAmountForAccountView(
    address _token,
    address _account
  ) internal view virtual returns (uint256 externalPendingAmount) {}

  /**
   * @notice Called upon token dispensation to allow custom logic.
   * @dev This function is virtual used as a hook for inheriting contracts
   * @param _token The token to send.
   * @param _receiver The receiver to send the tokens to.
   * @param amountDispensed The amount to be send.
   */
  function onTokenDispensedToReceiverInternal(
    address _token,
    address _account,
    address _receiver,
    uint256 amountDispensed
  ) internal virtual {}
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import {Ownable2Step, Ownable} from "@openzeppelin/contracts/access/Ownable2Step.sol";

import "../../../Lynx/interfaces/ILynxVersionedContract.sol";
import {IPushableDispenserV1} from "../interfaces/IPushableDispenserV1.sol";
import {IDispenserPullSourceV1} from "../interfaces/IDispenserPullSourceV1.sol";
import {TokenHandlingContractUtils} from "../../../Utils/TokenHandlingContractUtils.sol";
import {LexPoolEvents} from "../../../Lynx/interfaces/ILexPoolV1.sol";
import {AbstractPullSourceV1} from "./AbstractPullSourceV1.sol";

/**
 * @title GeneralTokenDispenserV1
 * @notice Functions as a single point for token dispensations from many sources
 */
contract GeneralTokenDispenserV1 is
  Ownable2Step,
  AbstractPullSourceV1,
  IPushableDispenserV1,
  ILynxVersionedContract
{
  string public constant CONTRACT_NAME = "GeneralTokenDispenserV1";
  string public constant CONTRACT_VERSION = "100"; // 0.1

  // ***** Enums *****

  enum DispensationPushLevel {
    NONE,
    BASE // Can push token dispensation plan but has to provide the tokens in the same tx
  }

  // ***** Events *****

  event TokenPulledForAccountFromSource(
    address indexed token,
    address indexed account,
    address indexed source,
    uint256 amount
  );
  event TokenPushedForAccount(
    string indexed dispensationKey,
    address indexed token,
    address indexed account,
    address pusher,
    uint256 round,
    uint256 amount
  );

  event IsAllClaimingPausedFlagSet(bool indexed value);
  event IsClaimingPausedForTokenFlagSet(
    address indexed token,
    bool indexed value
  );
  event PullSourceAdded(address indexed token, address indexed pullSource);
  event PullSourceRemoved(address indexed token, address indexed pullSource);
  event PusherRegistered(
    address indexed _token,
    address indexed _pusher,
    DispensationPushLevel level
  );
  event PusherUnregistered(address indexed _token, address indexed _pusher);

  event DispensationStarted(
    string indexed dispensationKey,
    address indexed token
  );
  event DispensationEnded(
    string indexed dispensationKey,
    address indexed token
  );
  event DispensationExplained(
    string indexed dispensationKey,
    address indexed token,
    string name,
    string description
  );

  event TokenAddedToEverDispensed(address indexed token);
  event TokenAddedToCurrentlyDispensed(address indexed token);
  event TokenRemovedFromCurrentlyDispensed(address indexed token);

  // ***** Modifiers *****

  modifier onlyRegisteredPusher(address _token, address _pusher) {
    require(isRegisteredPusher(_token, _pusher), "NOT_REGISTERED_PUSHER");
    _;
  }

  modifier whenClaimNotPaused(address _token) {
    require(!isAllClaimingPaused, "ALL_CLAIMING_PAUSED");
    require(!isClaimingPausedForToken[_token], "CLAIMING_PAUSED_FOR_TOKEN");
    _;
  }

  // ***** Storage *****

  // Allows to pause all claiming
  bool public isAllClaimingPaused;
  // Allows to pause claiming for specific tokens
  // token => flag
  mapping(address => bool) public isClaimingPausedForToken;

  // token => live contracts that can generate distributions events.
  mapping(address => address[]) public pullSourcesForToken;
  // token => poolSource => is pull source
  mapping(address => mapping(address => bool)) public isPullSourceForToken;

  // token => pushers
  mapping(address => address[]) public pushersForToken;

  // token => pusher => level
  mapping(address => mapping(address => DispensationPushLevel))
    public dispensationPusherLevels;

  address[] public allTokensCurrentlyDispensed;
  address[] public allTokensEverDispensed;
  // token => flag
  mapping(address => bool) public wasTokenEverDispensed;
  // token => flag
  mapping(address => bool) public isTokenCurrentlyDispensed;

  // token => account => historic amount of tokens dispensed to account
  mapping(address => mapping(address => uint256))
    public historicDispensationsForAccount;
  // token => account => historic amount of tokens dispensed to downstream dispense
  mapping(address => mapping(address => uint256))
    public downstreamDispensationsForAccount;

  // token => ongoing dispensation keys
  mapping(address => string[]) public allOngoingDispensationsForToken;

  // token => dispensation key => flag
  mapping(address => mapping(string => bool)) public isOngoingDispensation;
  // token => dispensation key => name
  mapping(address => mapping(string => string)) public dispensationNames;
  // token => dispensation key => description
  mapping(address => mapping(string => string)) public dispensationDescriptions;

  // ***** Views *****

  /**
   * @notice Returns the name of the contract
   */
  function getContractName() external pure returns (string memory) {
    return CONTRACT_NAME;
  }

  /**
   * @notice Returns the version of the contract
   * @dev units are scaled by 1000 (1,000 = 1.00, 1,120 = 1.12)
   */
  function getContractVersion() external pure returns (string memory) {
    return CONTRACT_VERSION;
  }

  function isPushableDispenserV1() external pure returns (bool) {
    return true;
  }

  function getPullSourcesForToken(
    address _token
  ) external view returns (address[] memory) {
    return pullSourcesForToken[_token];
  }

  function getPushersForToken(
    address _token
  ) external view returns (address[] memory) {
    return pushersForToken[_token];
  }

  function getDispensationPusherLevel(
    address _token,
    address _pusher
  ) external view returns (DispensationPushLevel) {
    return dispensationPusherLevels[_token][_pusher];
  }

  function getDispensationInfo(
    string calldata dispensationKey,
    address _token
  ) external view returns (string memory name, string memory description) {
    return (
      dispensationNames[_token][dispensationKey],
      dispensationDescriptions[_token][dispensationKey]
    );
  }

  function getAllTokensEverDispensed()
    external
    view
    returns (address[] memory)
  {
    return allTokensEverDispensed;
  }

  function getAllTokensCurrentlyDispensed()
    external
    view
    returns (address[] memory)
  {
    return allTokensCurrentlyDispensed;
  }

  function getAllOngoingDispensationsForToken(
    address _token
  ) external view returns (string[] memory) {
    return allOngoingDispensationsForToken[_token];
  }

  // ***** Constructor *****

  constructor() Ownable(msg.sender) {}

  // ***** Admin Functions *****

  function setDownstreamDispenser(
    address _downstreamDispenser
  ) external onlyOwner {
    setDownstreamDispenserInternal(_downstreamDispenser);
  }

  function setIsAllClaimingPaused(bool _value) external onlyOwner {
    // Validity
    require(isAllClaimingPaused != _value, "ALREADY_SET");

    // Storage
    isAllClaimingPaused = _value;

    // Event
    emit IsAllClaimingPausedFlagSet(isAllClaimingPaused);
  }

  function setIsClaimingPausedForToken(
    address _token,
    bool _value
  ) external onlyOwner {
    // Validity
    require(isClaimingPausedForToken[_token] != _value, "ALREADY_SET");

    // Storage
    isClaimingPausedForToken[_token] = _value;

    // Event
    emit IsClaimingPausedForTokenFlagSet(
      _token,
      isClaimingPausedForToken[_token]
    );
  }

  function addPullSource(
    address _token,
    address _pullSource
  ) external onlyOwner {
    // Sanity
    require(
      IDispenserPullSourceV1(_pullSource).isDispenserMatchingSourceV1(),
      "NOT_PULL_SOURCE_V1"
    );

    // Prevent duplications
    address[] storage pullSources = pullSourcesForToken[_token];
    for (uint i = 0; i < pullSources.length; i++) {
      require(pullSources[i] != _pullSource, "PULL_SOURCE_ALREADY_ADDED");
    }

    // Storage
    pullSourcesForToken[_token].push(_pullSource);
    isPullSourceForToken[_token][_pullSource] = true;

    // Internal registry
    addTokenToListsIfNeeded(_token);

    // Event
    emit PullSourceAdded(_token, _pullSource);
  }

  function removePullSource(
    address _token,
    address _pullSource
  ) external onlyOwner {
    bool pullSourceFound = false;

    // Storage
    address[] storage pullSources = pullSourcesForToken[_token];
    for (uint i = 0; i < pullSources.length; i++) {
      if (pullSources[i] == _pullSource) {
        pullSources[i] = pullSources[pullSources.length - 1];
        pullSources.pop();
        pullSourceFound = true;
        break;
      }
    }

    // Sanity
    require(pullSourceFound, "PULL_SOURCE_NOT_FOUND");

    // Storage
    isPullSourceForToken[_token][_pullSource] = false;

    // Internal registry
    removeTokenFromListsIfNeeded(_token);

    // Event
    emit PullSourceRemoved(_token, _pullSource);
  }

  function registerPusher(
    address _token,
    address _pusher,
    DispensationPushLevel level
  ) external onlyOwner {
    // Validation
    require(!isRegisteredPusher(_token, _pusher), "PUSHER_ALREADY_REGISTERED");
    require(level != DispensationPushLevel.NONE, "CANNOT_REGISTER_NONE_LEVEL");

    // Storage
    dispensationPusherLevels[_token][_pusher] = level;

    // Internal registry
    pushersForToken[_token].push(_pusher);
    addTokenToListsIfNeeded(_token);

    // Event
    emit PusherRegistered(_token, _pusher, level);
  }

  function unregisterPusher(
    address _token,
    address _pusher
  ) external onlyOwner {
    // Validation
    require(isRegisteredPusher(_token, _pusher), "PUSHER_NOT_REGISTERED");

    // Storage
    dispensationPusherLevels[_token][_pusher] = DispensationPushLevel.NONE;

    // Remove pusher from array
    address[] storage pushers = pushersForToken[_token];
    for (uint i = 0; i < pushers.length; i++) {
      if (pushers[i] == _pusher) {
        pushers[i] = pushers[pushers.length - 1];
        pushers.pop();
        break;
      }
    }

    // Internal registry
    removeTokenFromListsIfNeeded(_token);

    // Event
    emit PusherUnregistered(_token, _pusher);
  }

  function startDispensation(
    string calldata _dispensationKey,
    address _token,
    string calldata _name,
    string calldata _description
  ) external onlyOwner {
    // Validity
    require(
      !isOngoingDispensation[_token][_dispensationKey],
      "ALREADY_ONGOING"
    );

    // Storage
    isOngoingDispensation[_token][_dispensationKey] = true;
    allOngoingDispensationsForToken[_token].push(_dispensationKey);
    explainDispensationInternal(_dispensationKey, _token, _name, _description);

    // Event
    emit DispensationStarted(_dispensationKey, _token);
  }

  function endDispensation(
    string calldata _dispensationKey,
    address _token
  ) external onlyOwner {
    // Validity
    require(
      isOngoingDispensation[_token][_dispensationKey],
      "NOT_ONGOING_DISPENSATION"
    );

    // Storage
    isOngoingDispensation[_token][_dispensationKey] = false;

    string[]
      storage ongoingDispensationsForToken = allOngoingDispensationsForToken[
        _token
      ];

    for (uint i = 0; i < ongoingDispensationsForToken.length; i++) {
      if (
        keccak256(abi.encodePacked(ongoingDispensationsForToken[i])) ==
        keccak256(abi.encodePacked(_dispensationKey))
      ) {
        ongoingDispensationsForToken[i] = ongoingDispensationsForToken[
          ongoingDispensationsForToken.length - 1
        ];
        ongoingDispensationsForToken.pop();
        break;
      }
    }

    explainDispensationInternal(_dispensationKey, _token, "", "");

    // Event
    emit DispensationEnded(_dispensationKey, _token);
  }

  function explainDispensation(
    string calldata _dispensationKey,
    address _token,
    string calldata _name,
    string calldata _description
  ) external onlyOwner {
    explainDispensationInternal(_dispensationKey, _token, _name, _description);
  }

  /**
   * @notice Sweep any tokens from the contract
   * @dev Owner can sweep any token
   * @param _token The token to sweep
   * @param _amount The amount to sweep
   */
  function sweepTokens(address _token, uint256 _amount) external onlyOwner {
    sweepTokensInternal(_token, owner(), _amount);
  }

  function discreditAccount(
    address _token,
    address _account,
    uint256 _amount,
    address _destination
  ) external onlyOwner {
    discreditTokenInternal(_token, _account, _amount, _destination);
  }

  function discreditAccounts(
    address _token,
    address[] calldata _accounts,
    uint256[] calldata _amounts,
    address _destination
  ) external onlyOwner {
    // Sanity
    require(_accounts.length == _amounts.length, "LENGTH_MISMATCH");

    for (uint256 i = 0; i < _accounts.length; i++) {
      discreditTokenInternal(_token, _accounts[i], _amounts[i], _destination);
    }
  }

  // ***** Dispensation Functions *****

  function receiveDispensationPushForAccount(
    string calldata _dispensationKey,
    uint256 _dispensationRoundNumber,
    address _token,
    address _account,
    uint256 _amount
  ) external override {
    address pusher = msg.sender;
    // Ensure sender can push dispensation with given key
    require(isRegisteredPusher(_token, pusher), "PUSHER_NOT_REGISTERED");
    // Ensure dispensation is ongoing
    require(
      isOngoingDispensation[_token][_dispensationKey],
      "NOT_ONGOING_DISPENSATION"
    );

    // Sanity
    require(_amount > 0, "AMOUNT_ZERO");

    // Take tokens(if must)
    takeTokens(_token, msg.sender, _amount);

    registerDispensationPushForAccountInternal(
      _dispensationKey,
      _dispensationRoundNumber,
      _token,
      _account,
      _amount,
      pusher
    );
  }

  function receiveDispensationPushForAccounts(
    string calldata _dispensationKey,
    uint256 _dispensationRoundNumber,
    address _token,
    address[] calldata _accounts,
    uint256[] calldata _amounts
  ) external override {
    require(_accounts.length == _amounts.length, "LENGTH_MISMATCH");

    address pusher = msg.sender;

    // Ensure sender can push dispensation with given key
    require(isRegisteredPusher(_token, pusher), "PUSHER_NOT_REGISTERED");
    // Ensure dispensation is ongoing
    require(
      isOngoingDispensation[_token][_dispensationKey],
      "NOT_ONGOING_DISPENSATION"
    );

    // Calculate total amount
    uint256 totalAmount = 0;
    for (uint i = 0; i < _accounts.length; i++) {
      totalAmount += _amounts[i];
    }

    // Sanity
    require(totalAmount > 0, "AMOUNT_ZERO");

    // Take tokens
    takeTokens(_token, pusher, totalAmount);

    // Credit accounts
    for (uint256 i = 0; i < _accounts.length; i++) {
      registerDispensationPushForAccountInternal(
        _dispensationKey,
        _dispensationRoundNumber,
        _token,
        _accounts[i],
        _amounts[i],
        pusher
      );
    }
  }

  // ***** Claiming Functions *****

  /**
   * @notice Will dispense all pending _token to _account
   */
  function claimTokenFor(
    address _token,
    address _account
  ) external whenClaimNotPaused(_token) {
    // Dispense
    dispensePendingTokenToOwnerInternal(_token, _account);
  }

  /**
   * @notice Will pull from all known sources and then dispense all pending _token to _account
   */
  function pullAllAndClaimFor(
    address _token,
    address _account
  ) external whenClaimNotPaused(_token) {
    // Pull from all sources
    _pullFromAllSourcesAndRegisterInternal(_token, _account);

    // Dispense
    dispensePendingTokenToOwnerInternal(_token, _account);
  }

  /**
   * @notice Will pull from given _pullSources and then dispense all pending _token to _account
   */
  function pullSomeAndClaimFor(
    address _token,
    address _account,
    address[] calldata pullSources
  ) external whenClaimNotPaused(_token) {
    require(pullSources.length > 0, "NO_SOURCES_PROVIDED");

    // Pull from sources
    pullFromSomeSourcesAndRegisterInternal(pullSources, _token, _account);

    // Dispense
    dispensePendingTokenToOwnerInternal(_token, _account);
  }

  // ***** Internal Dispensation Functions *****

  /**
   * @notice Will send all of _account pending _token to _account
   */
  function dispensePendingTokenToOwnerInternal(
    address _token,
    address _account
  ) internal {
    dispensePendingTokenToReceiverInternal(_token, _account, _account);
  }

  /**
   * @notice Registers the dispensation in the historic records
   */
  function onTokenDispensedToReceiverInternal(
    address _token,
    address _account,
    address _receiver,
    uint256 amountDispensed
  ) internal override {
    if (_account == _receiver) {
      // Update historic dispensed amount
      historicDispensationsForAccount[_token][_account] += amountDispensed;
    } else {
      // Update downstream dispensed amount
      downstreamDispensationsForAccount[_token][_account] += amountDispensed;
    }
  }

  // ***** Internal Source Pulling Functions *****

  /**
   * @notice calls 'pullFromSourceAndRegisterInternal' for each listed source for _token
   */
  function _pullFromAllSourcesAndRegisterInternal(
    address _token,
    address _account
  ) internal override returns (uint256 externalPulledAmount) {
    address[] memory pullSources = pullSourcesForToken[address(_token)];

    externalPulledAmount = 0;

    for (uint i = 0; i < pullSources.length; i++) {
      externalPulledAmount += pullFromSourceAndRegisterInternal(
        IDispenserPullSourceV1(pullSources[i]),
        _token,
        _account
      );
    }
  }

  /**
   * @notice calls 'pullFromSourceAndRegisterInternal' for each listed source for _token
   */
  function _getExternalPendingAmountForAccountView(
    address _token,
    address _account
  ) internal view override returns (uint256 externalPendingAmount) {
    address[] memory pullSources = pullSourcesForToken[address(_token)];

    externalPendingAmount = 0;

    for (uint i = 0; i < pullSources.length; i++) {
      externalPendingAmount += IDispenserPullSourceV1(pullSources[i])
        .getPendingAmountForAccountView(_token, _account);
    }
  }

  /**
   * @notice calls 'pullFromSourceAndRegisterInternal' for each given source
   */
  function pullFromSomeSourcesAndRegisterInternal(
    address[] calldata _sources,
    address _token,
    address _account
  ) internal {
    for (uint i = 0; i < _sources.length; i++) {
      pullFromSourceAndRegisterInternal(
        IDispenserPullSourceV1(_sources[i]),
        _token,
        _account
      );
    }
  }

  /**
   * Pulls from _source and credits gains to _account
   */
  function pullFromSourceAndRegisterInternal(
    IDispenserPullSourceV1 _source,
    address _token,
    address _account
  ) internal returns (uint256 pulledAmount) {
    pulledAmount = pullFromSourceInternal(_source, _token, _account);
    creditTokenInternal(address(_token), _account, pulledAmount);
  }

  /**
   * @notice Pulls tokens from _source for _account
   * @return pulledAmount The tokens sent to this contract for _account
   */
  function pullFromSourceInternal(
    IDispenserPullSourceV1 _source,
    address _token,
    address _account
  ) internal returns (uint256 pulledAmount) {
    // Sanity -- ensuring source is registered
    require(
      isPullSourceForToken[address(_token)][address(_source)],
      "NOT_PULL_SOURCE"
    );

    uint256 balanceBefore = getSelfBalanceInTokenInternal(_token);
    _source.pullTokenForAccount(address(_token), _account);
    uint256 balanceAfter = getSelfBalanceInTokenInternal(_token);

    pulledAmount = balanceAfter - balanceBefore;

    emit TokenPulledForAccountFromSource(
      address(_token),
      _account,
      address(_source),
      pulledAmount
    );
  }

  // ***** Internal Push Receiving Functions *****

  /**
   * @notice Registers a push dispensation
   */
  function registerDispensationPushForAccountInternal(
    string calldata dispensationKey,
    uint256 dispensationRoundNumber,
    address _token,
    address _account,
    uint256 _amount,
    address _pusher
  ) internal {
    creditTokenInternal(_token, _account, _amount);

    emit TokenPushedForAccount(
      dispensationKey,
      _token,
      _account,
      _pusher,
      dispensationRoundNumber,
      _amount
    );
  }

  // ***** Internal Access Functions *****

  function isRegisteredPusher(
    address _token,
    address _pusher
  ) internal view returns (bool) {
    DispensationPushLevel pusherLevel = dispensationPusherLevels[_token][
      _pusher
    ];

    return pusherLevel != DispensationPushLevel.NONE;
  }

  // ***** Internal Registry Functions *****

  /**
   * @notice internal hook function to be called whenever a token source/pusher is added
   * @dev Call this function after adding source/pusher.
   */
  function addTokenToListsIfNeeded(address _token) internal {
    bool isCurrentlyDispensed = isTokenCurrentlyDispensed[_token];
    // If token is not currently dispensed, there is nothing more to do
    if (isCurrentlyDispensed) {
      return;
    }

    // Add to ever dispensed list
    if (!wasTokenEverDispensed[_token]) {
      // Storage
      allTokensEverDispensed.push(_token);
      wasTokenEverDispensed[_token] = true;

      // Event
      emit TokenAddedToEverDispensed(_token);
    }

    // Add to currently dispensed list
    if (!isCurrentlyDispensed) {
      // Storage
      allTokensCurrentlyDispensed.push(_token);
      isTokenCurrentlyDispensed[_token] = true;

      // Event
      emit TokenAddedToCurrentlyDispensed(_token);
    }
  }

  /**
   * @notice internal hook function to be called whenever a token source/pusher is removed
   * @dev Call this function after removing source/pusher.
   */
  function removeTokenFromListsIfNeeded(address _token) internal {
    uint256 pullSourcesCount = pullSourcesForToken[_token].length;
    uint256 pushersCount = pushersForToken[_token].length;

    if (pullSourcesCount == 0 && pushersCount == 0) {
      // Storage

      // Remove from currently dispensed list
      for (uint i = 0; i < allTokensCurrentlyDispensed.length; i++) {
        if (allTokensCurrentlyDispensed[i] == _token) {
          allTokensCurrentlyDispensed[i] = allTokensCurrentlyDispensed[
            allTokensCurrentlyDispensed.length - 1
          ];
          allTokensCurrentlyDispensed.pop();
          break;
        }
      }

      isTokenCurrentlyDispensed[_token] = false;

      // Event
      emit TokenRemovedFromCurrentlyDispensed(_token);
    }
  }

  function explainDispensationInternal(
    string calldata dispensationKey,
    address _token,
    string memory name,
    string memory description
  ) internal {
    // Storage
    dispensationNames[_token][dispensationKey] = name;
    dispensationDescriptions[_token][dispensationKey] = description;

    // Event
    emit DispensationExplained(dispensationKey, _token, name, description);
  }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

interface IDispenserPullSourceV1 {
  /**
   * @notice Returns whether the dispenser is matching the source.
   * @return True if interface matching
   */
  function isDispenserMatchingSourceV1() external pure returns (bool);

  /**
   * @notice Returns the pending amount for the account if it were to be pulled now.
   * @dev This function is not a view as it may update the state.
   * @param _token The token to get the pending amount for.
   * @param _account The account to get the pending amount for.
   * @return The pending amount for the account.
   */
  function getPendingAmountForAccount(
    address _token,
    address _account
  ) external returns (uint256);

  /**
   * @notice Returns the pending amount for the account if it were to be pulled now without any state.
   * @param _token The token to get the pending amount for.
   * @param _account The account to get the pending amount for.
   * @return The pending amount for the account.
   */
  function getPendingAmountForAccountView(
    address _token,
    address _account
  ) external view returns (uint256);

  /**
   * @notice Requests to receive the entire amount '_account' is entitled to
   * @dev This function is expected to be called by the dispenser, the source is expected to transfer tokens to the dispenser
   *      to be credited for '_account'
   * @param _token The token to request.
   * @param _account The account to request the token for.
   */
  function pullTokenForAccount(address _token, address _account) external;

  /**
   * @notice Requests to receive the entire amount '_account' is entitled to
   * @dev This function is expected to be called by the dispenser, the source is expected to transfer tokens to the dispenser
   *      to be credited for '_account'
   * @param _token The token to request.
   * @param _accounts The accounts to request the token for.
   * @return amounts an array of amounts that were pulled for each account
   */
  function pullTokenForAccounts(
    address _token,
    address[] calldata _accounts
  ) external returns (uint256[] memory amounts);
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

interface IPushableDispenserV1 {
  /**
   * @notice Flag for interface identification
   * @return True if interface matching
   */
  function isPushableDispenserV1() external pure returns (bool);

  /**
   * @notice Called by the Pusher, dispense should pull _amount from Pusher.
   * @dev The dispenser is expected to credit the account with the tokens pulled from sender
   * @param dispensationKey The dispensation key.
   * @param dispensationRoundNumber The dispensation round number, if valid.
   * @param _token The token to push.
   * @param _account The account to push the token for.
   * @param _amount The amount to push.
   */
  function receiveDispensationPushForAccount(
    string calldata dispensationKey,
    uint256 dispensationRoundNumber,
    address _token,
    address _account,
    uint256 _amount
  ) external;

  /**
   * @notice Called by the Pusher, dispense should pull SUM(_amounts) from Pusher.
   * @dev The dispenser is expected to credit the accounts with the tokens pulled from sender
   * @param dispensationKey The dispensation key.
   * @param dispensationRoundNumber The dispensation round number, if valid.
   * @param _token The token to push.
   * @param _accounts The accounts to push the token for.
   * @param _amounts The amounts to push per account.
   */
  function receiveDispensationPushForAccounts(
    string calldata dispensationKey,
    uint256 dispensationRoundNumber,
    address _token,
    address[] calldata _accounts,
    uint256[] calldata _amounts
  ) external;
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
 * @title TokenHandlingContractUtils
 * @notice Used to handle token related operations.
 * @dev DO NOT ADD STORAGE TO THIS CONTRACT
 */
contract TokenHandlingContractUtils {
  using SafeERC20 for IERC20;

  // ***** Events *****

  event TokensSwept(
    address indexed token,
    address indexed receiver,
    uint256 amount
  );

  // ***** Internal Util Functions *****

  /**
   * @notice Sweep any tokens from the contract
   * @param _token The token to sweep
   * @param _amount The amount to sweep
   */
  function sweepTokensInternal(
    address _token,
    address _receiver,
    uint256 _amount
  ) internal {
    uint256 amount = _amount > 0
      ? _amount
      : getSelfBalanceInTokenInternal(_token);

    sendTokens(_token, _receiver, amount);

    emit TokensSwept(address(_token), _receiver, amount);
  }

  /**
   * Utility function to safely take tokens (ERC20) from a pre-approved account
   * @dev Will revert if the contract will not get the exact 'amount' value
   */
  function takeTokens(address _token, address _from, uint256 _amount) internal {
    uint256 balanceBefore = getSelfBalanceInTokenInternal(_token);
    IERC20(_token).safeTransferFrom(_from, address(this), _amount);
    uint256 balanceAfter = getSelfBalanceInTokenInternal(_token);
    require(balanceAfter - balanceBefore == _amount, "DID_NOT_RECEIVE_EXACT");
  }

  /**
   * Utility function to safely send tokens (ERC20)
   */
  function sendTokens(address _token, address _to, uint256 _amount) internal {
    IERC20(_token).safeTransfer(_to, _amount);
  }

  function getSelfBalanceInTokenInternal(
    address _token
  ) internal view returns (uint256) {
    return IERC20(_token).balanceOf(address(this));
  }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):