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));
}
}