// SPDX-License-Identifier: MIT pragma solidity 0.8.19; import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Governable} from "../core/Governable.sol"; /** * @title CECDistributor * @dev CECDistributor is a contract for distributing CEC token with unlock time * after all data is set, transfer owner to timelock contract */ contract CECDistributor is ReentrancyGuard, Pausable, Ownable, Governable { using SafeERC20 for IERC20; struct ReleaseInfo { uint256 amount; uint256 releaseAllMonth; uint256 tgeRatio; uint256 lockDuration; } mapping(address account => ReleaseInfo info) public infoMap; mapping(address account => uint256 amount) public releasedMap; string public name; IERC20 public immutable cecToken; uint256 public constant DURATION = 86400 * 30; uint256 public start = 0; uint256 public constant TGE_PRECISION = 1000000; uint256 public lockDuration; event EventInfoUpdated(address indexed account, ReleaseInfo info); event EventCECClaimed(address indexed user, address indexed to, uint256 amount); event EventChangeAddress(address oldAddr, address newAddr); constructor( string memory _name, address _cecToken ) { name = _name; cecToken = IERC20(_cecToken); } /** * @dev Throws if called by any account other than the owner or gov. */ modifier ownerOrGov() { require(msg.sender == owner() || msg.sender == gov, "CECDistributor: forbidden"); _; } function setGov(address _gov) external override onlyOwner { gov = _gov; } /** * @dev update pause state * When encountering special circumstances that require an emergency pause of the contract, * the pause function can be called by the gov account to quickly pause the contract and minimize losses. */ function pause() external ownerOrGov { _pause(); } /** * @dev update unpause state */ function unpause() external ownerOrGov { _unpause(); } function setStart(uint256 newStart) external ownerOrGov { require(newStart > 0 && start == 0, "CECDistributor: it's already initialized"); start = newStart; } function withdrawToken(address to, uint256 amount) external onlyOwner { cecToken.safeTransfer(to, amount); } function updateInfo(address[] calldata accounts, ReleaseInfo[] calldata infos) external onlyOwner { require(accounts.length == infos.length, "CECDistributor: invalid input"); for (uint256 i = 0; i < accounts.length; i++) { infoMap[accounts[i]] = infos[i]; emit EventInfoUpdated(accounts[i], infos[i]); } } function calcClaimAmount(address user) public view whenNotPaused returns (uint256) { require(infoMap[user].amount > 0, "CECDistributor: not in whitelist"); require(block.timestamp >= start, "CECDistributor: not in claim time"); uint256 claimAmount = 0; uint256 tgeAmount = 0; ReleaseInfo memory info = infoMap[user]; if (info.tgeRatio > 0) { tgeAmount = ((info.amount * info.tgeRatio) / TGE_PRECISION); claimAmount += tgeAmount; } if (block.timestamp > start + info.lockDuration) { uint256 monthNum = (block.timestamp - start - info.lockDuration) / DURATION; if (monthNum <= info.releaseAllMonth) { claimAmount += (((info.amount - tgeAmount) * monthNum) / info.releaseAllMonth); } else { claimAmount = info.amount; } } claimAmount -= releasedMap[user]; return claimAmount; } function claim(address to) external nonReentrant whenNotPaused returns (uint256) { require(start > 0, "CECDistributor: start isn't init"); require(to != address(0), "CECDistributor: invalid address"); address _user = _msgSender(); uint256 amount = calcClaimAmount(_user); if (amount > 0) { releasedMap[_user] = releasedMap[_user] + amount; cecToken.safeTransfer(to, amount); emit EventCECClaimed(_user, to, amount); } return amount; } function changeAddress(address from, address to) external { require(infoMap[to].amount == 0, "CECDistributor: new addr is in whitelist"); require(infoMap[from].amount > 0, "CECDistributor: not in whitelist"); address _sender = _msgSender(); require(_sender == owner() || _sender == gov || _sender == from, "CECDistributor: sender not allowed"); infoMap[to] = infoMap[from]; delete infoMap[from]; releasedMap[to] = releasedMap[from]; releasedMap[from] = 0; emit EventChangeAddress(from, to); } }