-
Notifications
You must be signed in to change notification settings - Fork 0
/
ESE.sol
271 lines (230 loc) · 10 KB
/
ESE.sol
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
/**
* @dev Implementation of the {IERC20} interface with automatic vesting mechanism and EIP-2612 permit.
*/
contract ESE is ERC20Permit, ERC20Burnable {
struct Beneficiary{
uint256 amount;
address addr;
}
struct VestingParams{
uint256 amount;
uint128 cliff;
uint128 duration;// Duration without cliff
mapping(address => uint256) amounts;
}
struct ConstructorVestingParams{
uint32 cliff;
uint32 duration;
uint16 TGEMintShare;
}
///@dev Vesting parameters. Mappings are more gas efficient than arrays.
mapping(uint256 => VestingParams) public vestingStages;
///@dev Info on how many vesting stages there are in total.
uint256 public immutable vestingStagesLength;
///@dev Token generation event.
uint256 public TGE;
///@dev Tokens already released by vesting.
mapping(address => uint256) private _released;
///@dev Maps stage to its TGE mint share.
mapping(uint256 => uint256) private _TGEMintShares;
///@dev Is beneficiary added to the specified stage.
mapping(uint256 => mapping(address => bool)) private _isBeneficiaryAdded;
///@dev Total tokens that will be generated by vesting.
uint256 private _totalVesting;
///@dev Total tokens were released by vesting.
uint256 private _totalReleased;
///@dev Address who can add vesting beneficiaries and initialize this contract.
address private immutable _initializer;
///@dev Max ESE possible as specified in our tokenomics.
uint256 private constant MAX_ESE = 1_000_000_000 * (10**18);
uint256 private constant DENOMINATOR = 10000;
event Initialize(uint256 TGE);
event AddVestingBeneficiary(uint256 indexed stage, address indexed beneficiary, uint256 vestingAmount, uint256 TGEAmount);
modifier onlyBeforeInitialization() {
require(TGE == 0, "ESE: Already initialized");
require(msg.sender == _initializer, "ESE: Caller not _initializer");
_;
}
constructor(ConstructorVestingParams[] memory _vestingStages) ERC20("eesee","$ESE") ERC20Permit("eesee") {
require(_vestingStages.length <= 10, "ESE: Invalid vesting stages");
for (uint256 i; i < _vestingStages.length;) {
VestingParams storage crowdsale = vestingStages[i];
crowdsale.cliff = _vestingStages[i].cliff;
crowdsale.duration = _vestingStages[i].duration;
uint256 TGEMintShare = _vestingStages[i].TGEMintShare;
require(TGEMintShare <= DENOMINATOR, "ESE: Invalid TGEMintShare");
_TGEMintShares[i] = TGEMintShare;
unchecked{ ++i; }
}
_initializer = msg.sender;
vestingStagesLength = _vestingStages.length;
}
// =========== External Write Functions ===========
/**
* @dev Adds new vesting beneficiaries to {vestingStages}. Mints new tokens according to {_TGEMintShares} variable. Emits {AddVestingBeneficiary} event for each beneficiary.
* @param stage - vestingStages index to add beneficiaries for.
* @param beneficiaries - Beneficiary structs containing beneficiaries's addresses and vesting amounts.
* Note: Can only be called in an unitialized state by {_initializer}.
*/
function addVestingBeneficiaries(uint256 stage, Beneficiary[] calldata beneficiaries) external onlyBeforeInitialization {
require(stage < vestingStagesLength, "ESE: Invalid stage");
VestingParams storage crowdsale = vestingStages[stage];
uint256 TGEMintShare = _TGEMintShares[stage];
uint256 addedVestingAmount;
for(uint256 i; i < beneficiaries.length;){
address beneficiary = beneficiaries[i].addr;
require(beneficiary != address(0), "ESE: Invalid Beneficiary");
require(!_isBeneficiaryAdded[stage][beneficiary], "ESE: Beneficiary already added");
_isBeneficiaryAdded[stage][beneficiary] = true;
uint256 amount = beneficiaries[i].amount;
uint256 TGEMint = amount * TGEMintShare / DENOMINATOR;
if(TGEMint != 0){
_mint(beneficiary, TGEMint);
}
uint256 vestingAmount = amount - TGEMint;
crowdsale.amounts[beneficiary] = vestingAmount;
addedVestingAmount += vestingAmount;
unchecked{ ++i; }
emit AddVestingBeneficiary(stage, beneficiary, vestingAmount, TGEMint);
}
crowdsale.amount += addedVestingAmount;
_totalVesting += addedVestingAmount;
if(super.totalSupply() + _totalVesting > MAX_ESE) revert ("ESE: Overflow");
}
/**
* @dev Initializes this contract. Token transfers are not available until TGE timestamp. Emits {Initialize} event.
* @param _TGE - Token generation timestamp in the future. Set to 0 to initialize immediately.
* Note: Can only be called in an unitialized state by {_initializer}.
*/
function initialize(uint256 _TGE) external onlyBeforeInitialization {
if(_TGE == 0) {
_TGE = block.timestamp;
} else if(_TGE < block.timestamp || _TGE > block.timestamp + 90 days) revert ("ESE: Invalid TGE timestamp");
TGE = _TGE;
emit Initialize(_TGE);
}
// =========== External View Functions ===========
/**
* @dev Info on how many tokens have already been vested during the specified stage in total.
*/
function totalVestedAmount(uint256 stage) external view returns(uint256){
require(stage < vestingStagesLength, "ESE: Invalid stage");
return _totalVestedAmount(vestingStages[stage]);
}
/**
* @dev Info on how many tokens have already been vested during the specified stage for account.
*/
function vestedAmount(uint256 stage, address account) external view returns(uint256){
require(stage < vestingStagesLength, "ESE: Invalid stage");
return _vestedAmount(vestingStages[stage], account);
}
// =========== Public View Functions ===========
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view override returns (uint256) {
unchecked{
return super.totalSupply() + _totalReleasableAmount() - _totalReleased;
}
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view override returns (uint256) {
unchecked{
return super.balanceOf(account) + _releasableAmount(account);
}
}
// =========== Internal Write Functions ===========
/**
* @dev Mint vested tokens for from address. Update _totalReleased, _released[from] variables.
*/
function _beforeTokenTransfer(address from, address /*to*/, uint256 /*amount*/) internal override {
if(from != address(0)){
uint256 _TGE = TGE;
require(_TGE != 0 && _TGE <= block.timestamp, "ESE: TGE not started");
uint256 releasableAmount = _releasableAmount(from);
if(releasableAmount > 0){
_mint(from, releasableAmount);
unchecked {
_totalReleased += releasableAmount;
_released[from] += releasableAmount;
}
}
}
}
// =========== Internal View Functions ===========
/**
* @dev Calculates the amount that has already vested but hasn't been released yet.
*/
function _totalReleasableAmount() internal view returns(uint256 amount){
unchecked{
for(uint256 i; i < vestingStagesLength; ++i){
amount += _totalVestedAmount(vestingStages[i]);
}
}
}
/**
* @dev Calculates the amount that has already vested for a given vesting period in total.
* @param vesting - Vesting period to check.
*/
function _totalVestedAmount(VestingParams storage vesting) internal view returns (uint256) {
uint256 amount = vesting.amount;
if(amount == 0) {
return 0;
}
unchecked {
// Overflow not possible: vesting.cliff & vesting.duration are limited by 32 bits.
uint256 start = TGE + vesting.cliff;
if (block.timestamp < start) {
return 0;
}
uint256 duration = vesting.duration;
if (block.timestamp >= start + duration) {
return amount;
}
// Overflow not possible: Max ESE amount is limited by MAX_ESE.
return amount * (block.timestamp - start) / duration;
}
}
/**
* @dev Calculates the amount that has already vested but hasn't been released yet for an account.
* @param account - Address to check.
*/
function _releasableAmount(address account) internal view returns(uint256 amount){
unchecked{
for(uint256 i; i < vestingStagesLength; ++i){
amount += _vestedAmount(vestingStages[i], account);
}
amount -= _released[account];
}
}
/**
* @dev Calculates the amount that has already vested for a given vesting period for an account.
* @param vesting - Vesting period to check.
* @param account - Address to check.
*/
function _vestedAmount(VestingParams storage vesting, address account) internal view returns (uint256) {
uint256 amount = vesting.amounts[account];
if(amount == 0) {
return 0;
}
unchecked {
// Overflow not possible: vesting.cliff & vesting.duration are limited by 32 bits.
uint256 start = TGE + vesting.cliff;
if (block.timestamp < start) {
return 0;
}
uint256 duration = vesting.duration;
if (block.timestamp >= start + duration) {
return amount;
}
// Overflow not possible: Max ESE amount is limited by MAX_ESE.
return amount * (block.timestamp - start) / duration;
}
}
}