141 lines
4.7 KiB
Solidity
141 lines
4.7 KiB
Solidity
// 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);
|
|
}
|
|
}
|