tig-monorepo/tig-token/TokenLocker.sol
2025-01-17 00:11:57 +00:00

158 lines
6.4 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
interface ITokenLocker {
event TokensLocked(address indexed user, uint256 amount, uint256 locked);
event TokensUnlocked(address indexed user, uint256 amount, uint256 locked, uint256 withdrawableTime);
event TokensWithdrawn(address indexed user, uint256 amount);
event TokensRewarded(address indexed user, uint256 amount, uint256 claimable);
event TokensRelocked(address indexed user, uint256 amount, uint256 locked);
event TokensClaimed(address indexed user, uint256 amount, uint256 locked);
function getNumPendingWithdrawals(address account) external view returns (uint256);
function getTimeUntilWithdrawable(address account, uint256 index) external view returns (uint256);
function lock(uint256 amount) external;
function unlock(uint256 amount) external;
function withdraw(uint256 index) external;
function relock(uint256 index) external;
function rewardTokens(address account, uint256 amount) external;
function claim(uint256 amount) external;
}
contract TokenLocker is ITokenLocker, ReentrancyGuard, Ownable {
using SafeMath for uint256;
struct PendingWithdrawal {
uint256 amount;
uint256 timeWithdrawable;
}
IERC20 public immutable token;
uint256 public immutable pendingPeriod;
uint256 public totalLocked;
uint256 public totalPendingWithdrawal;
uint256 public totalClaimable;
mapping(address => uint256) public locked;
mapping(address => uint256) public claimable;
mapping(address => PendingWithdrawal[]) public pendingWithdrawals;
constructor(address tokenAddress, uint256 _pendingPeriod) public Ownable(msg.sender) {
token = IERC20(tokenAddress);
pendingPeriod = _pendingPeriod;
}
function getNumPendingWithdrawals(address account) external view override returns (uint256) {
return pendingWithdrawals[account].length;
}
function getTimeUntilWithdrawable(address account, uint256 index) external view returns (uint256) {
require(index < pendingWithdrawals[account].length, "Invalid withdrawal index");
uint256 withdrawableTime = pendingWithdrawals[account][index].timeWithdrawable;
if (block.timestamp >= withdrawableTime) {
return 0;
}
return withdrawableTime.sub(block.timestamp);
}
function lock(uint256 amount) external override nonReentrant {
require(amount > 0, "Amount must be greater than 0");
require(token.transferFrom(msg.sender, address(this), amount), "Transfer failed");
locked[msg.sender] = locked[msg.sender].add(amount);
totalLocked = totalLocked.add(amount);
emit TokensLocked(msg.sender, amount, locked[msg.sender]);
}
function unlock(uint256 amount) external override nonReentrant {
require(amount > 0, "Amount must be greater than 0");
require(locked[msg.sender] >= amount, "Insufficient locked balance");
locked[msg.sender] = locked[msg.sender].sub(amount);
totalLocked = totalLocked.sub(amount);
totalPendingWithdrawal = totalPendingWithdrawal.add(amount);
uint256 timeWithdrawable = block.timestamp.add(pendingPeriod);
pendingWithdrawals[msg.sender].push(PendingWithdrawal({
amount: amount,
timeWithdrawable: timeWithdrawable
}));
emit TokensUnlocked(msg.sender, amount, locked[msg.sender], timeWithdrawable);
}
function withdraw(uint256 index) external override nonReentrant {
require(index < pendingWithdrawals[msg.sender].length, "Invalid withdrawal index");
PendingWithdrawal[] storage userWithdrawals = pendingWithdrawals[msg.sender];
PendingWithdrawal memory withdrawal = userWithdrawals[index];
require(block.timestamp >= withdrawal.timeWithdrawable, "Withdrawal not yet available");
uint256 amount = withdrawal.amount;
// Swap and pop
userWithdrawals[index] = userWithdrawals[userWithdrawals.length - 1];
userWithdrawals.pop();
totalPendingWithdrawal = totalPendingWithdrawal.sub(amount);
require(token.transfer(msg.sender, amount), "Transfer failed");
emit TokensWithdrawn(msg.sender, amount);
}
function relock(uint256 index) external override nonReentrant {
require(index < pendingWithdrawals[msg.sender].length, "Invalid withdrawal index");
PendingWithdrawal[] storage userWithdrawals = pendingWithdrawals[msg.sender];
PendingWithdrawal memory withdrawal = userWithdrawals[index];
uint256 amount = withdrawal.amount;
// Swap and pop
userWithdrawals[index] = userWithdrawals[userWithdrawals.length - 1];
userWithdrawals.pop();
// Update state
totalPendingWithdrawal = totalPendingWithdrawal.sub(amount);
locked[msg.sender] = locked[msg.sender].add(amount);
totalLocked = totalLocked.add(amount);
emit TokensRelocked(msg.sender, amount, locked[msg.sender]);
}
function rewardTokens(address account, uint256 amount) external override onlyOwner nonReentrant {
require(account != address(0), "Invalid address");
require(amount > 0, "Amount must be greater than 0");
require(token.transferFrom(msg.sender, address(this), amount), "Transfer failed");
claimable[account] = claimable[account].add(amount);
totalClaimable = totalClaimable.add(amount);
emit TokensRewarded(account, amount, claimable[account]);
}
function claim(uint256 amount) external override nonReentrant {
require(amount > 0, "Amount must be greater than 0");
require(claimable[msg.sender] >= amount, "Insufficient claimable balance");
claimable[msg.sender] = claimable[msg.sender].sub(amount);
totalClaimable = totalClaimable.sub(amount);
locked[msg.sender] = locked[msg.sender].add(amount);
totalLocked = totalLocked.add(amount);
emit TokensClaimed(msg.sender, amount, locked[msg.sender]);
}
}