CoindepoPoolsVesting

Description:

ERC20 token contract with Factory capabilities. Standard implementation for fungible tokens on Ethereum.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "@openzeppelin/contracts/access/AccessControl.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (access/AccessControl.sol)

pragma solidity ^0.8.20;

import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../utils/introspection/ERC165.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```solidity
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```solidity
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
 * to enforce additional security measures for this role.
 */
abstract contract AccessControl is Context, IAccessControl, ERC165 {
    struct RoleData {
        mapping(address account => bool) hasRole;
        bytes32 adminRole;
    }

    mapping(bytes32 role => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with an {AccessControlUnauthorizedAccount} error including the required role.
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view virtual returns (bool) {
        return _roles[role].hasRole[account];
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
     * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
     * is missing `role`.
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert AccessControlUnauthorizedAccount(account, role);
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
        return _roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleGranted} event.
     */
    function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleRevoked} event.
     */
    function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `callerConfirmation`.
     *
     * May emit a {RoleRevoked} event.
     */
    function renounceRole(bytes32 role, address callerConfirmation) public virtual {
        if (callerConfirmation != _msgSender()) {
            revert AccessControlBadConfirmation();
        }

        _revokeRole(role, callerConfirmation);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        bytes32 previousAdminRole = getRoleAdmin(role);
        _roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleGranted} event.
     */
    function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
        if (!hasRole(role, account)) {
            _roles[role].hasRole[account] = true;
            emit RoleGranted(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Attempts to revoke `role` from `account` and returns a boolean indicating if `role` was revoked.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleRevoked} event.
     */
    function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
        if (hasRole(role, account)) {
            _roles[role].hasRole[account] = false;
            emit RoleRevoked(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }
}
"
    },
    "@openzeppelin/contracts/access/IAccessControl.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (access/IAccessControl.sol)

pragma solidity ^0.8.20;

/**
 * @dev External interface of AccessControl declared to support ERC-165 detection.
 */
interface IAccessControl {
    /**
     * @dev The `account` is missing a role.
     */
    error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);

    /**
     * @dev The caller of a function is not the expected one.
     *
     * NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
     */
    error AccessControlBadConfirmation();

    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted to signal this.
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call. This account bears the admin role (for the granted role).
     * Expected in cases where the role was granted using the internal {AccessControl-_grantRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `callerConfirmation`.
     */
    function renounceRole(bytes32 role, address callerConfirmation) external;
}
"
    },
    "@openzeppelin/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
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);
}
"
    },
    "@openzeppelin/contracts/utils/Context.sol": {
      "content": "// 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;
    }
}
"
    },
    "@openzeppelin/contracts/utils/introspection/ERC165.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}
"
    },
    "@openzeppelin/contracts/utils/introspection/IERC165.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
"
    },
    "contracts/CoindepoPoolsVesting/CoindepoPoolsVesting.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;

import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ICoindepoPoolsVesting} from "./ICoindepoPoolsVesting.sol";

/**
 * @title CoindepoPoolsVesting
 * @author PixelPlex.inc
 * @notice Vesting contract for multiple tokenomics pools with flexible parameters and individual beneficiaries.
 * @dev Pools can only be created before vesting is started (TGE). Unlocking can only be performed after start.
 *      All actions are restricted to ADMIN_ROLE.
 */
contract CoindepoPoolsVesting is ICoindepoPoolsVesting, AccessControl {
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");

    uint256 public constant MONTH = 30 days;

    IERC20 public immutable coinDepoToken;

    bool private _started;
    uint256 private _startedAt;
    uint256 private _totalLockAmount;

    mapping(bytes32 name => Pool) private _pools;

    /**
     * @inheritdoc ICoindepoPoolsVesting
     */
    function started() external view returns (bool status) {
        return _started;
    }

    /**
     * @inheritdoc ICoindepoPoolsVesting
     */
    function startedAt() external view returns (uint256 timestamp) {
        return _startedAt;
    }

    /**
     * @inheritdoc ICoindepoPoolsVesting
     */
    function totalLockAmount() external view returns (uint256 amount) {
        return _totalLockAmount;
    }

    /**
     * @inheritdoc ICoindepoPoolsVesting
     */
    function getPool(bytes32 name) external view returns (Pool memory pool) {
        return _pools[name];
    }

    /**
     * @inheritdoc ICoindepoPoolsVesting
     */
    function getAvailableUnlockAmount(bytes32 name) public view returns (uint256 amount) {
        Pool storage pool = _pools[name];
        require(pool.amount != 0, InvalidPoolName());
        amount = _availableToUnlock(pool);
    }

    /**
     * @inheritdoc ICoindepoPoolsVesting
     */
    function getAvailableUnlockAmounts(bytes32[] calldata names) external view returns (uint256[] memory amounts) {
        uint256 length = names.length;
        amounts = new uint256[](length);
        for (uint256 i = 0; i < length; i++) {
            amounts[i] = getAvailableUnlockAmount(names[i]);
        }
    }

    /**
     * @notice Initializes the contract with the specified parameters.
     * @dev Grants admin roles and sets immutable CoinDepo token address.
     *
     * Requirements:
     *  - `admin_` must not be the zero address.
     *  - `coinDepoToken_` must not be the zero address.
     *
     * @param admin_ Address to be granted admin and default admin roles.
     * @param coinDepoToken_ ERC20 token address for vesting.
     */
    constructor(address admin_, address coinDepoToken_) onlyValidAddress(admin_) onlyValidAddress(coinDepoToken_) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin_);
        _grantRole(ADMIN_ROLE, admin_);
        coinDepoToken = IERC20(coinDepoToken_);
    }

    /**
     * @inheritdoc ICoindepoPoolsVesting
     */
    function start(uint256 startTimestamp) external onlyRole(ADMIN_ROLE) onlyNotStarted returns (uint256 startedAt_) {
        uint256 totalLockAmount_ = _totalLockAmount;
        require(totalLockAmount_ != 0, NoPools());
        require(startTimestamp >= block.timestamp, InvalidStartTimestamp());
        _started = true;
        _startedAt = startedAt_ = startTimestamp;
        coinDepoToken.transferFrom(msg.sender, address(this), totalLockAmount_);
        emit VestingStarted(totalLockAmount_, startedAt_);
    }

    /**
     * @inheritdoc ICoindepoPoolsVesting
     */
    function createPool(PoolParams calldata params) external onlyRole(ADMIN_ROLE) onlyNotStarted returns (bool) {
        _createPool(params);
        return true;
    }

    /**
     * @inheritdoc ICoindepoPoolsVesting
     */
    function batchCreatePool(PoolParams[] calldata params) external onlyRole(ADMIN_ROLE) onlyNotStarted returns (bool) {
        uint256 length = params.length;
        for (uint256 i = 0; i < length; i++) {
            _createPool(params[i]);
        }
        return true;
    }

    /**
     * @inheritdoc ICoindepoPoolsVesting
     */
    function unlockTokens(bytes32 poolName) external onlyRole(ADMIN_ROLE) onlyStarted returns (uint256 unlockAmount) {
        unlockAmount = _unlockTokens(poolName);
    }

    /**
     * @inheritdoc ICoindepoPoolsVesting
     */
    function batchUnlockTokens(
        bytes32[] calldata poolNames
    ) external onlyRole(ADMIN_ROLE) onlyStarted returns (uint256 totalUnlockAmount) {
        uint256 length = poolNames.length;
        for (uint256 i = 0; i < length; i++) {
            totalUnlockAmount += _unlockTokens(poolNames[i]);
        }
    }

    /**
     * @notice Creates a new vesting pool in storage.
     * @dev Calculates and stores initial unlock and monthly unlock values. Emits {PoolCreated}.
     * @param params Pool parameters.
     */
    function _createPool(PoolParams calldata params) private {
        require(params.poolName != bytes32(0), InvalidPoolName());
        require(params.beneficiary != address(0), InvalidAddress());
        require(params.amount != 0, InvalidPoolAmount());
        require(params.initialUnlockX18 != 0 && params.initialUnlockX18 <= 1e18, InvalidPoolInitialUnlockX18());
        require(params.vesting != 0, InvalidVesting());
        Pool storage pool = _pools[params.poolName];
        require(pool.amount == 0, PoolAlreadyExists());
        pool.beneficiary = params.beneficiary;
        pool.amount = params.amount;
        pool.initialUnlockAmount = (params.amount * params.initialUnlockX18) / 1e18;
        pool.initialUnlockDelay = params.initialUnlockDelay;
        pool.vesting = params.vesting * MONTH;
        pool.monthlyUnlock = ((params.amount - pool.initialUnlockAmount) * MONTH) / pool.vesting;
        _totalLockAmount += params.amount;
        emit PoolCreated(params.poolName, pool);
    }

    /**
     * @notice Unlocks and transfers tokens for a specific pool.
     * @dev Transfers unlocked tokens to the beneficiary and updates withdrawn amount. Emits {Unlocked}.
     * @param poolName Name of the pool.
     * @return unlockAmount Amount of tokens transferred.
     */
    function _unlockTokens(bytes32 poolName) private returns (uint256 unlockAmount) {
        Pool storage pool = _pools[poolName];
        require(pool.amount != 0, InvalidPoolName());
        unlockAmount = _availableToUnlock(pool);
        if (unlockAmount != 0) {
            pool.withdrawn = pool.withdrawn + unlockAmount;
            coinDepoToken.transfer(pool.beneficiary, unlockAmount);
            emit Unlocked(poolName, unlockAmount);
        }
    }

    /**
     * @notice Calculates the amount of tokens available for unlocking for a pool.
     * @param pool Reference to pool struct.
     * @return unlockAmount Amount currently available for unlock.
     */
    function _availableToUnlock(Pool storage pool) private view returns (uint256 unlockAmount) {
        uint256 timestamp = block.timestamp;
        uint256 startTimestamp = _startedAt + pool.initialUnlockDelay;
        uint256 endTimestamp = startTimestamp + pool.vesting;
        if (timestamp < startTimestamp) return 0;
        else if (timestamp >= endTimestamp) return pool.amount - pool.withdrawn;
        else {
            uint256 closeMonths = (timestamp - startTimestamp) / MONTH;
            uint256 vestingClaimAmount = closeMonths * pool.monthlyUnlock;
            uint256 withdrawAmount = pool.initialUnlockAmount + vestingClaimAmount;
            unlockAmount = withdrawAmount <= pool.withdrawn ? 0 : withdrawAmount - pool.withdrawn;
        }
    }

    /**
     * @notice Modifier that checks that the passed address is not zero address.
     * @dev Checks the `address_` is not zero address.
     * @param address_ Address to check.
     */
    modifier onlyValidAddress(address address_) {
        require(address_ != address(0), InvalidAddress());
        _;
    }

    /**
     * @notice Modifier that allows functions to execute only if the lockup has started.
     * @dev Checks the `_started` flag to ensure the lockup process is active.
     */
    modifier onlyStarted() {
        require(_started, NotStarted());
        _;
    }

    /**
     * @notice Modifier that allows functions to execute only if the lockup has not started.
     * @dev Checks the `_started` flag to ensure the lockup process is inactive.
     */
    modifier onlyNotStarted() {
        require(!_started, AlreadyStarted());
        _;
    }
}
"
    },
    "contracts/CoindepoPoolsVesting/ICoindepoPoolsVesting.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

/**
 * @title ICoindepoPoolsVesting
 * @author PixelPlex.inc
 * @notice Interface for Coindepo tokenomics pools vesting contract.
 */
interface ICoindepoPoolsVesting {
    struct PoolParams {
        bytes32 poolName; ///< Unique name of the pool.
        address beneficiary; ///< Address receiving unlocked tokens.
        uint256 amount; ///< Total amount allocated to the pool.
        uint256 initialUnlockX18; ///< Initial unlock percentage (scaled by 1e18).
        uint256 initialUnlockDelay; ///< Delay (in seconds) before initial unlock.
        uint256 vesting; ///< Total vesting duration (in months).
    }

    struct Pool {
        address beneficiary; ///< Address receiving unlocked tokens.
        uint256 amount; ///< Total amount allocated to the pool.
        uint256 initialUnlockAmount; ///< Absolute value of initial unlock.
        uint256 initialUnlockDelay; ///< Delay (in seconds) before initial unlock.
        uint256 vesting; ///< Total vesting duration (in seconds).
        uint256 monthlyUnlock; ///< Amount unlocked every month (after initial unlock).
        uint256 withdrawn; ///< Total amount already withdrawn.
    }

    /**
     * @notice Emitted when vesting is started.
     * @param totalLockAmount Total amount of tokens locked for vesting.
     * @param startedAt Timestamp when vesting started.
     */
    event VestingStarted(uint256 totalLockAmount, uint256 startedAt);

    /**
     * @notice Emitted when a new pool is created.
     * @param poolName Unique name of the pool.
     * @param pool Pool struct with configuration parameters.
     */
    event PoolCreated(bytes32 indexed poolName, Pool pool);

    /**
     * @notice Emitted when tokens are unlocked for a pool.
     * @param poolName Name of the pool.
     * @param amount Amount of tokens unlocked.
     */
    event Unlocked(bytes32 indexed poolName, uint256 amount);

    /**
     * @notice Indicates that a zero address was provided where a valid address is required.
     */
    error InvalidAddress();

    /**
     * @notice Indicates that the provided pool name is invalid or zero.
     */
    error InvalidPoolName();

    /**
     * @notice Indicates that the provided pool amount is invalid or zero.
     */
    error InvalidPoolAmount();

    /**
     * @notice Indicates that the provided initial unlock value is invalid or zero.
     */
    error InvalidPoolInitialUnlockX18();

    /**
     * @notice Indicates that the provided vesting count is zero.
     */
    error InvalidVesting();

    /**
     * @notice Indicates that a pool with the specified name already exists.
     */
    error PoolAlreadyExists();

    /**
     * @notice Indicates that no vesting pools have been created yet.
     */
    error NoPools();

    /**
     * @notice Indicates that the provided start timestamp is invalid (lt now).
     */
    error InvalidStartTimestamp();

    /**
     * @notice Indicates that vesting has not started yet.
     */
    error NotStarted();

    /**
     * @notice Indicates that vesting has already started.
     */
    error AlreadyStarted();

    /**
     * @notice Returns true if vesting has started.
     * @return status True if vesting started, false otherwise.
     */
    function started() external view returns (bool status);

    /**
     * @notice Returns the timestamp when vesting started.
     * @return timestamp Timestamp of vesting start, or 0 if not started.
     */
    function startedAt() external view returns (uint256 timestamp);

    /**
     * @notice Returns the total amount of tokens locked for all pools.
     * @return amount Total locked amount.
     */
    function totalLockAmount() external view returns (uint256 amount);

    /**
     * @notice Returns pool configuration and state by pool name.
     * @param name Pool name.
     * @return pool Pool struct.
     */
    function getPool(bytes32 name) external view returns (Pool memory pool);

    /**
     * @notice Returns the currently available amount for unlocking for a given pool.
     * @param name Pool name.
     * @return amount Amount available for unlock.
     */
    function getAvailableUnlockAmount(bytes32 name) external view returns (uint256 amount);

    /**
     * @notice Returns available amounts for unlocking for multiple pools.
     * @param names Array of pool names.
     * @return amounts Array of amounts available for unlock.
     */
    function getAvailableUnlockAmounts(bytes32[] calldata names) external view returns (uint256[] memory amounts);

    /**
     * @notice Starts the vesting schedule and locks all tokens for all created pools.
     * @dev
     *  - Transfers the total required amount of tokens from the caller to the contract.
     *  - Sets the vesting start timestamp for all pools.
     *  - Emits a {VestingStarted} event.
     *
     * Requirements:
     *  - Must be called only once, before vesting is started.
     *  - There must be at least one pool created.
     *  - The caller must approve the contract to spend at least the total required token amount.
     *  - All required tokens must be successfully transferred from the caller to the contract.
     *
     * @param startTimestamp The timestamp when vesting starts.
     * @return startedAt_ The timestamp when vesting started.
     */
    function start(uint256 startTimestamp) external returns (uint256 startedAt_);

    /**
     * @notice Creates a new vesting pool with the given parameters.
     * @dev
     *  - Registers a new pool in storage using provided parameters.
     *  - Calculates initial and monthly unlock values based on input.
     *  - Increases the global total lock amount by the pool's amount.
     *  - Emits a {PoolCreated} event.
     *
     * Requirements:
     *  - Must be called before vesting is started.
     *  - Pool name must not be zero.
     *  - Beneficiary address must not be zero.
     *  - Amount must not be zero.
     *  - Initial unlock value (initialUnlockX18) must not be zero.
     *  - A pool with the same name must not already exist.
     *
     * @param params The pool parameters (name, beneficiary, amount, initial unlock %, delay, vesting duration).
     * @return success True if the pool was successfully created.
     */
    function createPool(PoolParams calldata params) external returns (bool success);

    /**
     * @notice Creates multiple vesting pools in a single transaction.
     * @dev
     *  - Registers each pool using the provided parameters.
     *  - Each pool is created according to the same requirements as {createPool}.
     *  - Increases the total lock amount by the sum of amounts for all pools.
     *  - Emits a {PoolCreated} event for each created pool.
     *
     * Requirements:
     *  - Must be called before vesting is started.
     *  - Each pool in the input array must satisfy all requirements of {createPool}.
     *
     * @param params Array of pool parameters (name, beneficiary, amount, initial unlock %, delay, vesting).
     * @return success True if all pools were successfully created.
     */
    function batchCreatePool(PoolParams[] calldata params) external returns (bool success);

    /**
     * @notice Unlocks and transfers available tokens for a specific pool to its beneficiary.
     * @dev
     *  - Calculates the currently available amount to unlock for the given pool.
     *  - Transfers unlocked tokens to the beneficiary address of the pool.
     *  - Updates the withdrawn amount in the pool.
     *  - Emits an {Unlocked} event on successful unlock.
     *
     * Requirements:
     *  - Vesting must have started.
     *  - Pool with the given name must exist.
     *  - Pool must have available tokens to unlock.
     *
     * @param poolName The name of the pool for which to unlock tokens.
     * @return unlockAmount The amount of tokens unlocked and transferred.
     */
    function unlockTokens(bytes32 poolName) external returns (uint256 unlockAmount);

    /**
     * @notice Unlocks and transfers available tokens for multiple pools in a batch operation.
     * @dev
     *  - For each pool in the input array, calculates the available amount to unlock and transfers tokens to its
     *    beneficiary.
     *  - Updates the withdrawn amount for each pool.
     *  - Emits an {Unlocked} event for each successful unlock.
     *
     * Requirements:
     *  - Vesting must have started.
     *  - Each pool in the input array must exist.
     *  - Each pool must have available tokens to unlock.
     *
     * @param poolNames Array of pool names for which to unlock tokens.
     * @return totalUnlockAmount The sum of all tokens unlocked and transferred in this batch.
     */
    function batchUnlockTokens(bytes32[] calldata poolNames) external returns (uint256 totalUnlockAmount);
}
"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "evmVersion": "paris",
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "metadata": {
      "useLiteralContent": true
    }
  }
}}

Tags:
ERC20, ERC165, Token, Factory|addr:0x958d515c2e74c5be85093d7ecd6dea4644932ef7|verified:true|block:23419044|tx:0xa57dc5b9f237e8659bc10a5f4b698ee1acd451e61d68ba28ab576236b9c3b8b7|first_check:1758553485

Submitted on: 2025-09-22 17:04:45

Comments

Log in to comment.

No comments yet.