-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSimpleTokenVesting.sol
More file actions
122 lines (89 loc) · 3.41 KB
/
SimpleTokenVesting.sol
File metadata and controls
122 lines (89 loc) · 3.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
interface IERC20 {
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
function balanceOf(address user) external view returns (uint256);
}
contract SimpleTokenVesting {
IERC20 public immutable token;
address public immutable owner;
uint256 public immutable start;
uint256 public immutable duration;
uint256 public totalAllocation;
uint256 public totalClaimed;
bool public initialized;
mapping(address => uint256) public allocation;
mapping(address => uint256) public claimed;
error NotOwner();
error NotInitialized();
error AlreadyInitialized();
error ZeroAddress();
error ZeroAmount();
error BadArrayLength();
error NotEnoughTokensInContract();
error NothingToClaim();
event Initialized(uint256 totalAllocation);
event AllocationSet(address indexed user, uint256 amount);
event Claimed(address indexed user, uint256 amount);
constructor(address tokenAddress, uint256 _start, uint256 _duration) {
if (tokenAddress == address(0)) revert ZeroAddress();
if (_duration == 0) revert ZeroAmount();
token = IERC20(tokenAddress);
owner = msg.sender;
start = _start;
duration = _duration;
}
modifier onlyOwner() {
if (msg.sender != owner) revert NotOwner();
_;
}
function setAllocations(address[] calldata users, uint256[] calldata amounts) external onlyOwner {
if (initialized) revert AlreadyInitialized();
if (users.length != amounts.length) revert BadArrayLength();
uint256 sum;
for (uint256 i = 0; i < users.length; i++) {
address u = users[i];
uint256 a = amounts[i];
if (u == address(0)) revert ZeroAddress();
allocation[u] = a;
sum += a;
emit AllocationSet(u, a);
}
totalAllocation = sum;
if (token.balanceOf(address(this)) < totalAllocation) revert NotEnoughTokensInContract();
initialized = true;
emit Initialized(totalAllocation);
}
function vestedAmount(address user) public view returns (uint256) {
if (!initialized) return 0;
uint256 alloc = allocation[user];
if (alloc == 0) return 0;
if (block.timestamp <= start) return 0;
uint256 elapsed = block.timestamp - start;
if (elapsed >= duration) return alloc;
return (alloc * elapsed) / duration;
}
function claimable(address user) public view returns (uint256) {
uint256 vested = vestedAmount(user);
uint256 already = claimed[user];
if (vested <= already) return 0;
return vested - already;
}
function claim() external {
if (!initialized) revert NotInitialized();
uint256 amount = claimable(msg.sender);
if (amount == 0) revert NothingToClaim();
claimed[msg.sender] += amount;
totalClaimed += amount;
bool ok = token.transfer(msg.sender, amount);
require(ok);
emit Claimed(msg.sender, amount);
}
function rescueTokens(address to, uint256 amount) external onlyOwner {
if (to == address(0)) revert ZeroAddress();
if (amount == 0) revert ZeroAmount();
bool ok = token.transfer(to, amount);
require(ok);
}
}