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/CoindepoReserves/CoindepoReserves.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 {ICoindepoReserves} from "./ICoindepoReserves.sol";
/**
* @title CoindepoReserves
* @author PixelPlex.inc
* @notice Contract for managing vesting and distribution of Coindepo reserves (bonuses and community reserve).
* @dev Uses a two-phase vesting logic: phase one claims daily for N days, phase two claims daily for M days.
*/
contract CoindepoReserves is ICoindepoReserves, AccessControl {
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
uint256 public constant ONE_DAY = 1 days;
uint256 public constant ONE_MONTH = 30 days;
IERC20 public immutable coinDepoToken;
bool private _started;
uint256 private _startedAt;
uint256 private _totalLockAmount;
uint256 private immutable _phaseOneTotalX18;
uint256 private immutable _phaseOneDuration;
uint256 private immutable _phaseTwoTotalWindows;
uint256 private immutable _phaseTwoWindowX18;
CustodyPart private _custodyPartEarnedBonuses;
CustodyPart private _custodyPartReserves;
mapping(address user => EarnedBonusVestingData) private _userToEarnBonusVestingData;
/**
* @inheritdoc ICoindepoReserves
*/
function started() external view returns (bool status) {
return _started;
}
/**
* @inheritdoc ICoindepoReserves
*/
function startedAt() external view returns (uint256 timestamp) {
return _startedAt;
}
/**
* @inheritdoc ICoindepoReserves
*/
function totalLockAmount() external view returns (uint256 amount) {
return _totalLockAmount;
}
/**
* @inheritdoc ICoindepoReserves
*/
function phaseOneTotalX18() external view returns (uint256 phaseOneShareX18) {
return _phaseOneTotalX18;
}
/**
* @inheritdoc ICoindepoReserves
*/
function phaseOneDuration() external view returns (uint256 duration) {
return _phaseOneDuration;
}
/**
* @inheritdoc ICoindepoReserves
*/
function phaseTwoDuration() external view returns (uint256 duration) {
return _phaseTwoTotalWindows * ONE_MONTH;
}
/**
* @inheritdoc ICoindepoReserves
*/
function phaseTwoTotalWindows() external view returns (uint256 countOfWindows) {
return _phaseTwoTotalWindows;
}
/**
* @inheritdoc ICoindepoReserves
*/
function phaseTwoWindowX18() external view returns (uint256 windowShareX18) {
return _phaseTwoWindowX18;
}
/**
* @inheritdoc ICoindepoReserves
*/
function getCustodyPartEarnedBonuses() external view returns (CustodyPart memory) {
return _custodyPartEarnedBonuses;
}
/**
* @inheritdoc ICoindepoReserves
*/
function getCustodyPartReserves() external view returns (CustodyPart memory) {
return _custodyPartReserves;
}
/**
* @inheritdoc ICoindepoReserves
*/
function getEarnBonusVestingDataByUser(address user) external view returns (EarnedBonusVestingData memory) {
return _userToEarnBonusVestingData[user];
}
/**
* @inheritdoc ICoindepoReserves
*/
function getAvailableCustodyPartEarnedBonusesClaimAmount() external view returns (uint256 amount) {
CustodyPart memory custodyPart = _custodyPartEarnedBonuses;
amount = _availableToClaim(
custodyPart.amount,
custodyPart.phaseOneTotalAmount,
custodyPart.phaseOneDaily,
custodyPart.phaseTwoWindowAmount,
custodyPart.withdrawn
);
}
/**
* @inheritdoc ICoindepoReserves
*/
function getAvailableCustodyPartReservesClaimAmount() external view returns (uint256 amount) {
CustodyPart memory custodyPart = _custodyPartReserves;
amount = _availableToClaim(
custodyPart.amount,
custodyPart.phaseOneTotalAmount,
custodyPart.phaseOneDaily,
custodyPart.phaseTwoWindowAmount,
custodyPart.withdrawn
);
}
/**
* @inheritdoc ICoindepoReserves
*/
function getAvailableUserEarnedBonusClaimAmount(address user) external view returns (uint256 amount) {
EarnedBonusVestingData memory data = _userToEarnBonusVestingData[user];
require(data.amount != 0, HasNoVesting());
amount = _availableToClaim(
data.amount,
data.phaseOneTotalAmount,
data.phaseOneDaily,
data.phaseTwoWindowAmount,
data.withdrawn
);
}
/**
* @notice Initializes the contract with the specified parameters.
* @dev Sets up roles, immutable references and global phase params.
*
* Requirements:
* - `admin_` must not be zero address.
* - `coinDepoToken_` must not be zero address.
* - Vesting parameters must be valid:
* - `phaseOneTotalX18_` > 0 and <= 1e18.
* - `phaseOneDuration_` > 0.
* - `phaseTwoTotalWindows_` > 0.
* - `phaseTwoWindowX18_` > 0 and <= 1e18.
* - (`phaseTwoTotalWindows_` * `phaseTwoWindowX18_`) == 1e18.
*
* @param admin_ The address to be granted admin rights.
* @param coinDepoToken_ The ERC20 token used for distribution.
* @param phaseOneTotalX18_ The X18 fixed-point ratio (parts per 1e18) of phase one allocation.
* @param phaseOneDuration_ Duration of phase one in seconds.
* @param phaseTwoTotalWindows_ Number of vesting windows in phase two.
* @param phaseTwoWindowX18_ Fraction (X18) of the phase two allocation for each window
* (sum of all windows must be 1e18).
*/
constructor(
address admin_,
address coinDepoToken_,
uint256 phaseOneTotalX18_,
uint256 phaseOneDuration_,
uint256 phaseTwoTotalWindows_,
uint256 phaseTwoWindowX18_
)
onlyValidAddress(admin_)
onlyValidAddress(coinDepoToken_)
onlyValidPhasesConfiguration(phaseOneTotalX18_, phaseOneDuration_, phaseTwoTotalWindows_, phaseTwoWindowX18_)
{
_grantRole(DEFAULT_ADMIN_ROLE, admin_);
_grantRole(ADMIN_ROLE, admin_);
coinDepoToken = IERC20(coinDepoToken_);
_phaseOneTotalX18 = phaseOneTotalX18_;
_phaseOneDuration = phaseOneDuration_;
_phaseTwoTotalWindows = phaseTwoTotalWindows_;
_phaseTwoWindowX18 = phaseTwoWindowX18_;
}
/**
* @inheritdoc ICoindepoReserves
*/
function start(uint256 startTimestamp) external onlyRole(ADMIN_ROLE) onlyNotStarted returns (uint256 startedAt_) {
uint256 totalLockAmount_ = _totalLockAmount;
require(totalLockAmount_ != 0, NoReserves());
require(startTimestamp >= block.timestamp, InvalidStartTimestamp());
_started = true;
_startedAt = startedAt_ = startTimestamp;
coinDepoToken.transferFrom(msg.sender, address(this), totalLockAmount_);
emit VestingStarted(totalLockAmount_, startedAt_);
}
/**
* @inheritdoc ICoindepoReserves
*/
function createCustodyPartEarnedBonuses(
address beneficiary,
uint256 amount
)
external
onlyRole(ADMIN_ROLE)
onlyNotStarted
onlyValidAddress(beneficiary)
onlyValidAmount(amount)
returns (bool)
{
CustodyPart storage custodyPart = _custodyPartEarnedBonuses;
_createCustodyPart(custodyPart, beneficiary, amount);
emit CustodyPartEarnedBonusesCreated(custodyPart);
return true;
}
/**
* @inheritdoc ICoindepoReserves
*/
function createCustodyPartReserves(
address beneficiary,
uint256 amount
)
external
onlyRole(ADMIN_ROLE)
onlyNotStarted
onlyValidAddress(beneficiary)
onlyValidAmount(amount)
returns (bool)
{
CustodyPart storage custodyPart = _custodyPartReserves;
_createCustodyPart(custodyPart, beneficiary, amount);
emit CustodyPartReservesCreated(custodyPart);
return true;
}
/**
* @inheritdoc ICoindepoReserves
*/
function createEarnBonusVestingData(
address user,
uint256 amount
) external onlyNotStarted onlyRole(ADMIN_ROLE) returns (bool) {
_createEarnBonusVestingData(user, amount);
return true;
}
/**
* @inheritdoc ICoindepoReserves
*/
function batchCreateEarnBonusVestingData(
address[] calldata users,
uint256[] calldata amounts
) external onlyNotStarted onlyRole(ADMIN_ROLE) returns (bool) {
uint256 length = users.length;
require(length == amounts.length, InvalidBatchInput(length, amounts.length));
for (uint256 i = 0; i < length; i++) {
_createEarnBonusVestingData(users[i], amounts[i]);
}
return true;
}
/**
* @inheritdoc ICoindepoReserves
*/
function claimCustodyPartEarnedBonuses() external onlyRole(ADMIN_ROLE) onlyStarted returns (uint256 claimAmount) {
CustodyPart storage custodyPart = _custodyPartEarnedBonuses;
claimAmount = _availableToClaim(
custodyPart.amount,
custodyPart.phaseOneTotalAmount,
custodyPart.phaseOneDaily,
custodyPart.phaseTwoWindowAmount,
custodyPart.withdrawn
);
if (claimAmount != 0) {
custodyPart.withdrawn = custodyPart.withdrawn + claimAmount;
coinDepoToken.transfer(custodyPart.beneficiary, claimAmount);
emit CustodyPartEarnedBonusesClaimed(claimAmount);
}
}
/**
* @inheritdoc ICoindepoReserves
*/
function claimCustodyPartReserves() external onlyRole(ADMIN_ROLE) onlyStarted returns (uint256 claimAmount) {
CustodyPart storage custodyPart = _custodyPartReserves;
claimAmount = _availableToClaim(
custodyPart.amount,
custodyPart.phaseOneTotalAmount,
custodyPart.phaseOneDaily,
custodyPart.phaseTwoWindowAmount,
custodyPart.withdrawn
);
if (claimAmount != 0) {
custodyPart.withdrawn = custodyPart.withdrawn + claimAmount;
coinDepoToken.transfer(custodyPart.beneficiary, claimAmount);
emit CustodyPartReservesClaimed(claimAmount);
}
}
/**
* @inheritdoc ICoindepoReserves
*/
function claimEarnedBonusesVesting() external onlyStarted returns (uint256 claimAmount) {
address caller = msg.sender;
EarnedBonusVestingData storage data = _userToEarnBonusVestingData[caller];
require(data.amount != 0, HasNoVesting());
claimAmount = _availableToClaim(
data.amount,
data.phaseOneTotalAmount,
data.phaseOneDaily,
data.phaseTwoWindowAmount,
data.withdrawn
);
if (claimAmount != 0) {
data.withdrawn = data.withdrawn + claimAmount;
coinDepoToken.transfer(caller, claimAmount);
emit EarnedBonusVestingDataClaimed(caller, claimAmount);
}
}
/**
* @dev Internal helper to initialize and configure a custody part structure.
* Used for both custody lines (earned bonuses and reserves).
* @param custodyPart Storage pointer to the custody part struct.
* @param beneficiary Address that will receive tokens from this custody part.
* @param amount Total amount allocated for this custody part.
*/
function _createCustodyPart(CustodyPart storage custodyPart, address beneficiary, uint256 amount) private {
require(custodyPart.amount == 0, CustodyPartAlreadyExists(custodyPart));
custodyPart.beneficiary = beneficiary;
custodyPart.amount = amount;
(uint256 phaseOneTotalAmount, uint256 phaseTwoTotalAmount) = _calculatePhasesAmount(amount);
custodyPart.phaseOneTotalAmount = phaseOneTotalAmount;
custodyPart.phaseOneDaily = (phaseOneTotalAmount * ONE_DAY) / _phaseOneDuration;
custodyPart.phaseTwoWindowAmount = (phaseTwoTotalAmount * _phaseTwoWindowX18) / 1e18;
_totalLockAmount += amount;
}
/**
* @dev Internal helper to create vesting data for user's earned bonuses.
* Writes data to storage and emits creation event.
* @param user User address for whom the vesting data is created.
* @param amount Total vesting amount for the user.
*/
function _createEarnBonusVestingData(
address user,
uint256 amount
) private onlyValidAddress(user) onlyValidAmount(amount) {
EarnedBonusVestingData storage data = _userToEarnBonusVestingData[user];
require(data.amount == 0, EarnedBonusVestingDataAlreadyExists(user, data));
data.amount = amount;
(uint256 phaseOneTotalAmount, uint256 phaseTwoTotalAmount) = _calculatePhasesAmount(amount);
data.phaseOneTotalAmount = phaseOneTotalAmount;
data.phaseOneDaily = (phaseOneTotalAmount * ONE_DAY) / _phaseOneDuration;
data.phaseTwoWindowAmount = (phaseTwoTotalAmount * _phaseTwoWindowX18) / 1e18;
_totalLockAmount += amount;
emit EarnedBonusVestingDataCreated(user, data);
}
/**
* @dev Splits a total amount into phase one and phase two allocation
* according to the current vesting configuration (X18 fixed-point share).
* @param amount The total amount to be split.
* @return phaseOneTotalAmount Amount allocated to phase one.
* @return phaseTwoTotalAmount Amount allocated to phase two.
*/
function _calculatePhasesAmount(
uint256 amount
) private view returns (uint256 phaseOneTotalAmount, uint256 phaseTwoTotalAmount) {
phaseOneTotalAmount = (amount * _phaseOneTotalX18) / 1e18;
phaseTwoTotalAmount = amount - phaseOneTotalAmount;
}
/**
* @dev Calculates the currently claimable amount for a vesting entry,
* according to the configured two-phase schedule and already withdrawn amount.
* @param amount Total allocation for this entry.
* @param phaseOneTotalAmount Amount assigned to phase one (daily unlocking).
* @param phaseOneDaily Daily claimable amount during phase one.
* @param phaseTwoWindowAmount Amount unlocked per window in phase two.
* @param withdrawn Amount already withdrawn/claimed.
* @return claimAmount Currently available amount for claim, after accounting for withdrawn.
*/
function _availableToClaim(
uint256 amount,
uint256 phaseOneTotalAmount,
uint256 phaseOneDaily,
uint256 phaseTwoWindowAmount,
uint256 withdrawn
) private view returns (uint256 claimAmount) {
uint256 timestamp = block.timestamp;
uint256 phaseOneStartTimestamp = _startedAt;
uint256 phaseTwoStartTimestamp = phaseOneStartTimestamp + _phaseOneDuration;
uint256 phaseTwoEndTimestamp = phaseTwoStartTimestamp + ONE_MONTH * _phaseTwoTotalWindows;
if (timestamp < phaseOneStartTimestamp) return 0;
else if (timestamp >= phaseTwoEndTimestamp) return amount - withdrawn;
else if (timestamp < phaseTwoStartTimestamp) {
uint256 closedDaysInPhaseOne = (timestamp - phaseOneStartTimestamp) / ONE_DAY;
claimAmount = phaseOneDaily * closedDaysInPhaseOne;
} else {
claimAmount = phaseOneTotalAmount; // phase one amount
uint256 passedTimeInPhaseTwo = timestamp - phaseTwoStartTimestamp;
uint256 closedWindowsInPhaseTwo = passedTimeInPhaseTwo / ONE_MONTH; // number of fully elapsed phase two windows
uint256 closedDaysInCurrentWindow = (passedTimeInPhaseTwo % ONE_MONTH) / ONE_DAY; // number of closed days in current window
claimAmount += phaseTwoWindowAmount * closedWindowsInPhaseTwo; // amount for closed windows
claimAmount += (phaseTwoWindowAmount * closedDaysInCurrentWindow) / (ONE_MONTH / ONE_DAY); // amount for closed days in current window
}
claimAmount = claimAmount <= withdrawn ? 0 : claimAmount - withdrawn;
}
/**
* @notice Checks the correctness of vesting phase parameters.
* @dev Ensures that:
* - phaseOneTotalX18_ is not zero (non-zero phase one ratio, X18 fixed point).
* - phaseOneDuration_ is not zero (non-zero phase one duration).
* - phaseTwoTotalWindows_ is not zero (non-zero number of phase two windows).
* - phaseTwoWindowX18_ is not zero (non-zero window share for phase two).
* - The sum of all phase two windows strictly equals 1e18 (i.e. 100% of phase two allocation is distributed).
* @param phaseOneTotalX18_ Ratio of phase one allocation, X18 fixed point.
* @param phaseOneDuration_ Duration of phase one in seconds.
* @param phaseTwoTotalWindows_ Number of vesting windows in phase two.
* @param phaseTwoWindowX18_ Fraction of phase two allocation per window, X18 fixed point.
*/
modifier onlyValidPhasesConfiguration(
uint256 phaseOneTotalX18_,
uint256 phaseOneDuration_,
uint256 phaseTwoTotalWindows_,
uint256 phaseTwoWindowX18_
) {
require(
phaseOneTotalX18_ != 0 &&
phaseOneTotalX18_ <= 1e18 &&
phaseOneDuration_ != 0 &&
phaseTwoTotalWindows_ != 0 &&
phaseTwoWindowX18_ != 0 &&
phaseTwoWindowX18_ <= 1e18 &&
phaseTwoTotalWindows_ * phaseTwoWindowX18_ == 1e18,
InvalidPhasesConfiguration(phaseOneTotalX18_, phaseOneDuration_, phaseTwoTotalWindows_, phaseTwoWindowX18_)
);
_;
}
/**
* @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 checks that the passed amount is not zero.
* @dev Checks that `amount` is greater than zero.
* @param amount Amount to check.
*/
modifier onlyValidAmount(uint256 amount) {
require(amount != 0, InvalidAmount());
_;
}
/**
* @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/CoindepoReserves/ICoindepoReserves.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
/**
* @title ICoindepoReserves
* @author PixelPlex.inc
* @notice Interface for the CoindepoReserves contract with split vesting logic.
*/
interface ICoindepoReserves {
struct CustodyPart {
address beneficiary; ///< Address receiving claimed tokens for the custody part.
uint256 amount; ///< Total allocation for the custody part.
uint256 phaseOneTotalAmount; ///< Total amount for phase one.
uint256 phaseOneDaily; ///< Amount claimed daily during phase one.
uint256 phaseTwoWindowAmount; ///< Amount for each window during phase two.
uint256 withdrawn; ///< Total amount already claimed.
}
struct EarnedBonusVestingData {
uint256 amount; ///< Total allocation for the user.
uint256 phaseOneTotalAmount; ///< Total amount for phase one.
uint256 phaseOneDaily; ///< Amount claimed daily during phase one.
uint256 phaseTwoWindowAmount; ///< Amount for each window during phase two.
uint256 withdrawn; ///< Total amount already claimed.
}
/**
* @notice Emitted when the vesting process is started.
* @param totalLockAmount Total amount of tokens locked for distribution.
* @param startedAt Timestamp when vesting starts.
*/
event VestingStarted(uint256 totalLockAmount, uint256 startedAt);
/**
* @notice Emitted when the custody part for earned bonuses is created.
* @param custodyPart Struct containing details about the custody allocation.
*/
event CustodyPartEarnedBonusesCreated(CustodyPart custodyPart);
/**
* @notice Emitted when the custody part for reserves is created.
* @param custodyPart Struct containing details about the reserve allocation.
*/
event CustodyPartReservesCreated(CustodyPart custodyPart);
/**
* @notice Emitted when vesting data for earned bonuses is created for a user.
* @param user Address of the user.
* @param vestingBonusesData Struct containing user's vesting allocation details.
*/
event EarnedBonusVestingDataCreated(address indexed user, EarnedBonusVestingData vestingBonusesData);
/**
* @notice Emitted when tokens are claimed from the earned bonuses custody part.
* @param amount Amount of tokens claimed.
*/
event CustodyPartEarnedBonusesClaimed(uint256 amount);
/**
* @notice Emitted when tokens are claimed from the reserves custody part.
* @param amount Amount of tokens claimed.
*/
event CustodyPartReservesClaimed(uint256 amount);
/**
* @notice Emitted when a user claims tokens from their earned bonus vesting allocation.
* @param user Address of the user claiming tokens.
* @param amount Amount of tokens claimed.
*/
event EarnedBonusVestingDataClaimed(address indexed user, uint256 amount);
/**
* @notice Indicates that phases configuration was provided with invalid values.
* @param phaseOneTotalX18 Total amount for phase one in X18 format.
* @param phaseOneDuration Duration of phase one in seconds.
* @param phaseTwoTotalWindows Number of vesting windows in phase two.
* @param phaseTwoWindowX18 Fraction (X18) of the phase two allocation for each window.
*/
error InvalidPhasesConfiguration(
uint256 phaseOneTotalX18,
uint256 phaseOneDuration,
uint256 phaseTwoTotalWindows,
uint256 phaseTwoWindowX18
);
/**
* @notice Indicates that custody part already exists and cannot be created again.
* @param custodyPart Struct containing the existing custody part details.
*/
error CustodyPartAlreadyExists(CustodyPart custodyPart);
/**
* @notice Indicates that vesting data for a user already exists and cannot be created again.
* @param user Address of the user for whom vesting data already exists.
* @param vestingData Existing vesting data for the user.
*/
error EarnedBonusVestingDataAlreadyExists(address user, EarnedBonusVestingData vestingData);
/**
* @notice Indicates that there are no reserves to distribute or totalLockAmount is zero.
*/
error NoReserves();
/**
* @notice Indicates that the user has no vesting allocation.
*/
error HasNoVesting();
/**
* @notice Indicates that a zero address was provided where a valid address is required.
*/
error InvalidAddress();
/**
* @notice Indicates that an invalid (zero) amount was provided where a positive amount is required.
*/
error InvalidAmount();
/**
* @notice Indicates that the lengths of the batch input arrays do not match.
* @param usersLength Length of the users array.
* @param amountLength Length of the amounts array.
*/
error InvalidBatchInput(uint256 usersLength, uint256 amountLength);
/**
* @notice Indicates that the provided start timestamp is invalid.
*/
error InvalidStartTimestamp();
/**
* @notice Indicates that vesting has not started yet.
*/
error NotStarted();
/**
* @notice Indicates that vesting has already started.
*/
error AlreadyStarted();
/**
* @notice Returns whether the vesting process has started.
* @return status True if vesting is active, false otherwise.
*/
function started() external view returns (bool status);
/**
* @notice Returns the timestamp when the vesting process started.
* @return timestamp The Unix timestamp of vesting start.
*/
function startedAt() external view returns (uint256 timestamp);
/**
* @notice Returns the total amount of tokens locked for vesting.
* @return amount The total lock amount in tokens.
*/
function totalLockAmount() external view returns (uint256 amount);
/**
* @notice Returns the total amount of tokens allocated for phase one in X18 format.
* @return phaseOneShareX18 The total amount of tokens allocated for phase one in X18 format.
*/
function phaseOneTotalX18() external view returns (uint256 phaseOneShareX18);
/**
* @notice Returns the duration of the first phase (in seconds).
* @return duration The duration of phase one.
*/
function phaseOneDuration() external view returns (uint256 duration);
/**
* @notice Returns the total duration of phase two in seconds.
* @dev Duration is calculated as phaseTwoTotalWindows × ONE_MONTH (30 days).
* @return duration Total seconds in phase two.
*/
function phaseTwoDuration() external view returns (uint256 duration);
/**
* @notice Returns the total number of vesting windows in phase two.
* @return countOfWindows Number of phase two windows.
*/
function phaseTwoTotalWindows() external view returns (uint256 countOfWindows);
/**
* @notice Returns the X18 fraction representing the share of each window in phase two.
* @dev The sum over all windows must be 1e18.
* @return windowShareX18 Fraction of phase two allocation for each window (X18).
*/
function phaseTwoWindowX18() external view returns (uint256 windowShareX18);
/**
* @notice Returns the custody part details for earned bonuses.
* @return Struct with data about the earned bonuses custody part.
*/
function getCustodyPartEarnedBonuses() external view returns (CustodyPart memory);
/**
* @notice Returns the custody part details for the reserves.
* @return Struct with data about the reserves custody part.
*/
function getCustodyPartReserves() external view returns (CustodyPart memory);
/**
* @notice Returns the vesting data for earned bonuses for a specific user.
* @param user Address of the user to query.
* @return Struct with the user's earned bonus vesting data.
*/
function getEarnBonusVestingDataByUser(address user) external view returns (EarnedBonusVestingData memory);
/**
* @notice Returns the currently available claimable amount for the custody part of earned bonuses.
* @dev Calculates the amount that can be claimed by the beneficiary of the earned bonuses custody part,
* based on the vesting schedule and amount already withdrawn.
* @return amount The currently available claimable amount for the earned bonuses custody part.
*/
function getAvailableCustodyPartEarnedBonusesClaimAmount() external view returns (uint256 amount);
/**
* @notice Returns the currently available claimable amount for the custody part of reserves.
* @dev Calculates the amount that can be claimed by the beneficiary of the reserves custody part,
* based on the vesting schedule and amount already withdrawn.
* @return amount The currently available claimable amount for the reserves custody part.
*/
function getAvailableCustodyPartReservesClaimAmount() external view returns (uint256 amount);
/**
* @notice Returns the currently available claimable amount for a user's earned bonus vesting data.
* @dev Calculates the amount that a user can claim from their earned bonus vesting,
* based on the vesting schedule and amount already withdrawn.
* Reverts if the user does not have any vesting data.
* @param user The address of the user whose vesting claimable amount is being calculated.
* @return amount The currently available claimable amount for the user's earned bonus vesting.
*/
function getAvailableUserEarnedBonusClaimAmount(address user) external view returns (uint256 amount);
/**
* @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 the custody part for fixed (earned) bonuses.
* @dev
* - Allocates a new custody part for the specified beneficiary and amount.
* - Configures vesting logic for this custody part.
* - Emits a {CustodyPartEarnedBonusesCreated} event.
*
* Requirements:
* - Can only be called before vesting is started.
* - Only callable by an account with the ADMIN_ROLE.
* - The beneficiary address must not be zero.
* - The amount must be greater than zero.
* - The custody part must not already exist.
*
* @param beneficiary Address to receive tokens from this custody part.
* @param amount Total amount allocated for this custody part.
* @return True if the custody part was successfully created.
*/
function createCustodyPartEarnedBonuses(address beneficiary, uint256 amount) external returns (bool);
/**
* @notice Creates the custody part for community reserves.
* @dev
* - Allocates a new custody part for the specified beneficiary and amount.
* - Configures vesting logic for this custody part.
* - Emits a {CustodyPartReservesCreated} event.
*
* Requirements:
* - Can only be called before vesting is started.
* - Only callable by an account with the ADMIN_ROLE.
* - The beneficiary address must not be zero.
* - The amount must be greater than zero.
* - The custody part must not already exist.
*
* @param beneficiary Address to receive tokens from this custody part.
* @param amount Total amount allocated for this custody part.
* @return True if the custody part was successfully created.
*/
function createCustodyPartReserves(address beneficiary, uint256 amount) external returns (bool);
/**
* @notice Creates earned bonus vesting data for a user.
* @dev
* - Allocates and configures vesting data for the specified user and amount.
* - Emits an {EarnedBonusVestingDataCreated} event.
*
* Requirements:
* - Can only be called before vesting is started.
* - Only callable by an account with the ADMIN_ROLE.
* - The user address must not be zero.
* - The amount must be greater than zero.
* - Vesting data for this user must not already exist.
*
* @param user Address of the user for whom vesting data is being created.
* @param amount Total amount of earned bonuses allocated for this user.
* @return True if vesting data was successfully created.
*/
function createEarnBonusVestingData(address user, uint256 amount) external returns (bool);
/**
* @notice Creates earned bonus vesting data for multiple users in a single transaction.
* @dev
* - Allocates and configures vesting data for each user and amount in the input arrays.
* - Emits an {EarnedBonusVestingDataCreated} event for each successfully created entry.
*
* Requirements:
* - Can only be called before vesting is started.
* - Only callable by an account with the ADMIN_ROLE.
* - The `users` and `amounts` arrays must have the same length and must not be empty.
* - Each user address must not be zero.
* - Each amount must be greater than zero.
* - Vesting data for each user must not already exist.
*
* @param users Array of user addresses for which vesting data will be created.
* @param amounts Array of amounts of earned bonuses to allocate to each user.
* @return True if all vesting data entries were successfully created.
*/
function batchCreateEarnBonusVestingData(
address[] calldata users,
uint256[] calldata amounts
) external returns (bool);
/**
* @notice Claims the available amount of tokens for the earned bonuses custody part.
* @dev
* - Transfers the currently claimable tokens from the contract to the custody beneficiary.
* - Emits a {CustodyPartEarnedBonusesClaimed} event on successful claim.
*
* Requirements:
* - Only callable by an account with the ADMIN_ROLE.
* - Vesting must have started.
* - There must be tokens available to claim.
*
* @return claimAmount The amount of tokens successfully claimed.
*/
function claimCustodyPartEarnedBonuses() external returns (uint256 claimAmount);
/**
* @notice Claims the available amount of tokens for the reserves custody part.
* @dev
* - Transfers the currently claimable tokens from the contract to the reserves beneficiary.
* - Emits a {CustodyPartReservesClaimed} event on successful claim.
*
* Requirements:
* - Only callable by an account with the ADMIN_ROLE.
* - Vesting must have started.
* - There must be tokens available to claim.
*
* @return claimAmount The amount of tokens successfully claimed.
*/
function claimCustodyPartReserves() external returns (uint256 claimAmount);
/**
* @notice Claims the available amount of tokens for the caller's earned bonuses vesting.
* @dev
* - Transfers the currently claimable tokens from the contract to the caller.
* - Emits an {EarnedBonusVestingDataClaimed} event on successful claim.
*
* Requirements:
* - Vesting must have started.
* - The caller must have a valid vesting entry.
* - There must be tokens available to claim.
*
* @return claimAmount The amount of tokens successfully claimed.
*/
function claimEarnedBonusesVesting() external returns (uint256 claimAmount);
}
"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"metadata": {
"useLiteralContent": true
}
}
}}
Submitted on: 2025-09-22 16:21:18
Comments
Log in to comment.
No comments yet.