-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLinearCliffTimelock.sol
155 lines (125 loc) · 4.58 KB
/
LinearCliffTimelock.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
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.14;
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/security/ReentrancyGuard.sol';
import '@openzeppelin/contracts/access/AccessControlEnumerable.sol';
import './ILinearCliffTimelock.sol';
import './TimeContext.sol';
contract LinearCliffTimelock is
ILinearCliffTimelock,
ReentrancyGuard,
AccessControlEnumerable,
TimeContext
{
string private constant ERROR_ALREADY_INITIALIZED =
'ERROR_ALREADY_INITIALIZED';
string private constant ERROR_NOT_INITIALIZED = 'ERROR_NOT_INITIALIZED';
string private constant ERROR_NOT_YET = 'ERROR_NOT_YET';
string private constant ERROR_EMPTY = 'ERROR_EMPTY';
string private constant ERROR_EDGE_BT_END = 'ERROR_EDGE_BT_END';
string private constant ERROR_EDGE_LT_NOW = 'ERROR_EDGE_LT_NOW';
string private constant ERROR_CANNOT_REVOKE = 'ERROR_CANNOT_REVOKE';
bytes32 private constant INITIALIZE_ROLE = keccak256('INITIALIZE_ROLE');
bytes32 private constant WITHDRAW_ROLE = keccak256('WITHDRAW_ROLE');
IERC20 public token;
address public beneficiary;
uint256 public totalLocked;
uint256 public cliffStart; // timestamp of lock start in seconds
uint256 public cliffTimePeriod; // number of seconds for each cliff
uint256 public cliffEdge; // timestamp of next cliff release in seconds
uint256 public cliffEnd; // timestamp of lock end in seconds
bool public initialized;
uint256 public cliffAmount; // amount to be released at each cliff
modifier mustBeInitialized() {
require(initialized, ERROR_NOT_INITIALIZED);
_;
}
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, _msgSender());
_grantRole(INITIALIZE_ROLE, _msgSender());
}
/**
* @dev Avoids revoking `WITHDRAW_ROLE` from the beneficiary
* @inheritdoc AccessControl
*/
function revokeRole(bytes32 role, address account)
public
virtual
override(IAccessControl, AccessControl)
{
// TODO: turn this into a require when sober
if (role == WITHDRAW_ROLE && account == beneficiary) {
revert(ERROR_CANNOT_REVOKE);
}
super.revokeRole(role, account);
}
function initialize(
IERC20 _token,
uint256 _amount,
address _sender,
address _beneficiary,
uint256 _cliffStart,
uint256 _cliffEnd,
uint256 _cliffTimePeriod
) external virtual nonReentrant onlyRole(INITIALIZE_ROLE) {
require(!initialized, ERROR_ALREADY_INITIALIZED);
uint256 edge = _cliffStart + _cliffTimePeriod;
require(edge >= _blockTimestamp(), ERROR_EDGE_LT_NOW);
require(edge <= _cliffEnd, ERROR_EDGE_BT_END);
_token.transferFrom(_sender, address(this), _amount);
_grantRole(WITHDRAW_ROLE, _beneficiary);
beneficiary = _beneficiary;
token = _token;
totalLocked = _amount;
cliffStart = _cliffStart;
cliffEdge = edge;
cliffEnd = _cliffEnd;
cliffTimePeriod = _cliffTimePeriod;
uint256 numCliffs = (cliffEnd - cliffStart) / cliffTimePeriod;
cliffAmount = totalLocked / numCliffs;
initialized = true;
emit OnInitialized(
token,
beneficiary,
totalLocked,
cliffStart,
cliffEnd,
cliffTimePeriod
);
}
function withdraw()
external
virtual
onlyRole(WITHDRAW_ROLE)
nonReentrant
mustBeInitialized
{
uint256 _now = _blockTimestamp();
if (_now >= cliffEnd) {
uint256 _balance = balance();
require(_balance > 0, ERROR_EMPTY);
token.transfer(beneficiary, _balance);
emit OnWithdraw(_balance, 0);
return;
}
require(_now >= cliffEdge, ERROR_NOT_YET);
uint256 numPastCliffs = ((_now - cliffEdge) / cliffTimePeriod) + 1;
uint256 amount = numPastCliffs * cliffAmount;
token.transfer(beneficiary, amount);
cliffEdge += numPastCliffs * cliffTimePeriod;
emit OnWithdraw(amount, cliffEdge);
}
function balance() public view mustBeInitialized returns (uint256) {
return token.balanceOf(address(this));
}
function supportsInterface(bytes4 interfaceId)
public
view
override(IERC165, AccessControlEnumerable)
returns (bool)
{
return
interfaceId == type(ILinearCliffTimelock).interfaceId ||
AccessControlEnumerable.supportsInterface(interfaceId);
}
}